[
  {
    "path": ".cargo/config.toml",
    "content": "[target.wasm32-unknown-unknown]\nrunner = 'wasm-bindgen-test-runner'\n\n[target.x86_64-pc-windows-msvc]\n# https://github.com/rust-lang/rust/issues/141626#issuecomment-2919988483\nlinker = \"rust-lld\"\n\n[resolver]\nincompatible-rust-versions = \"fallback\"\n"
  },
  {
    "path": ".config/insta.yaml",
    "content": "behavior:\n  # Disabling because of issues with running on Windows\n  # force_update: true\nreview:\n  # The default (true) has a small performance hit and never needed for us\n  warn_undiscovered: false\n  include_ignored: false\n  include_hidden: false\n"
  },
  {
    "path": ".config/lychee.toml",
    "content": "# Lychee link checker configuration\n# See: https://github.com/lycheeverse/lychee\n\n# Retry configuration for network errors\nmax_retries = 5\nretry_wait_time = 30  # seconds between retries\n\n# Timeout configuration\ntimeout = 20  # seconds\n\n# Maximum redirects to follow\nmax_redirects = 5\n\n# Accept these status codes as valid\naccept = [200, 206, 429]\n\n# Exclude patterns - URLs to ignore\nexclude = [\n  # Sites that block GitHub Actions or have reliability issues\n  \"^https://twitter\\\\.com\",\n  \"^https://www\\\\.cs\\\\.ox\\\\.ac\\\\.uk\",\n  \"^https://www\\\\.php\\\\.net\",\n  \"^https://github\\\\.com/.*container\",\n  \"^https://benn\\\\.substack\\\\.com\",\n  \"^https://stackoverflow\\\\.com\",\n  \"^https://www\\\\.npmjs\\\\.com\",\n  \"^https://invent\\\\.kde\\\\.org\",\n  \"^https://news\\\\.ycombinator\\\\.com\",\n  \"^https://repology\\\\.org\",\n]\n\n# Use cache to reduce rate limiting issues\ncache = true\nmax_cache_age = \"1d\"\n"
  },
  {
    "path": ".config/nextest.toml",
    "content": "[profile.default]\nfail-fast = false\nfailure-output = \"final\"\nslow-timeout = { period = \"500ms\" }\n\n[[profile.default.overrides]]\n# compileall does many passes over the same query, so it takes a bit longer\nfilter = 'package(prqlc) & test(queries::)'\nslow-timeout = { period = \"2s\" }\n\n[[profile.default.overrides]]\n# compileall does many passes over the same query, so it takes a bit longer\nfilter = 'package(prqlc) & test(queries::compileall::)'\nslow-timeout = { period = \"10s\" }\n\n[[profile.default.overrides]]\nfilter = 'package(prqlc) & test(queries::results::)'\nslow-timeout = { period = \"10s\" }\ntest-group = 'test-dbs'\n\n[test-groups.test-dbs]\n# test-dbs runs database setup when the connection is established, and because\n# nextest runs test in separate processes, this happens on every test. To\n# prevent multiple setups running at once, we set max-threads to 1. Ideally, we\n# could run tests in parallel and they would use a locking mechanism to see if\n# the database has already been setup. For now, we can use cargo test instead.\nmax-threads = 1\n\n[[profile.default.overrides]]\n# cli tests take a bit longer\nfilter = 'test(cli)'\nslow-timeout = { period = \"2s\" }\n\n[[profile.default.overrides]]\nfilter = 'package(mdbook-prql)'\n# These are testing dozens of queries, so we give them more time.\nslow-timeout = { period = \"20s\" }\ntest-group = 'docs-mdbook'\n\n[test-groups.docs-mdbook]\nmax-threads = 'num-cpus'\n"
  },
  {
    "path": ".config/vscode-recommended/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"lldb\",\n      \"request\": \"launch\",\n      \"name\": \"prqlc _a.prql\",\n      \"program\": \"${workspaceFolder}/target/debug/prqlc\",\n      \"args\": [\"debug\", \"semantics\", \"_a.prql\"],\n      \"env\": {\n        \"RUST_LOG\": \"debug\",\n        \"RUST_BACKTRACE\": \"1\"\n      },\n      \"preLaunchTask\": \"prqlc-build\",\n      \"cwd\": \"${workspaceFolder}\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".config/vscode-recommended/settings.json",
    "content": "{\n  \"files.exclude\": {\n    \"**/.git\": true,\n    \"**/.svn\": true,\n    \"**/.hg\": true,\n    \"**/CVS\": true,\n    \"**/.DS_Store\": true,\n    \"**/Thumbs.db\": true,\n    \"web/book/book\": true,\n    \"web/build\": true,\n    \".direnv\": true,\n    \".pytest_cache\": true,\n    \".mypy_cache\": true\n  }\n}\n"
  },
  {
    "path": ".config/vscode-recommended/tasks.json",
    "content": "{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"label\": \"prqlc-build\",\n      \"type\": \"cargo\",\n      \"command\": \"build\",\n      \"args\": [\"--package=prqlc\"],\n      \"presentation\": {\n        \"reveal\": \"silent\",\n        \"clear\": true\n      },\n      \"problemMatcher\": [\"$rustc\"]\n    }\n  ]\n}\n"
  },
  {
    "path": ".config/wt.toml",
    "content": "# Worktrunk configuration for PRQL\n#\n# Available template variables:\n#   {{ repo }}      - Repository name\n#   {{ branch }}    - Branch name (slashes replaced with dashes)\n#   {{ worktree }}  - Path to the new worktree\n#   {{ repo_root }} - Path to the main repository root\n\n# Post-start commands run in parallel after switching to a worktree\n[post-start]\n# Seed compiled dependencies from main worktree using CoW (copy-on-write)\n# Copies essential files, skipping incremental build objects\n# Result: warm-start builds instead of cold compiles\n# macOS: uses cp -c (clonefile on APFS)\ndeps = \"\"\"\n[ -d {{ repo_path }}/target/debug/deps ] && [ ! -e target ] &&\nmkdir -p target/debug/deps &&\ncp -c {{ repo_path }}/target/debug/deps/*.rlib {{ repo_path }}/target/debug/deps/*.rmeta target/debug/deps/ &&\ncp -cR {{ repo_path }}/target/debug/.fingerprint {{ repo_path }}/target/debug/build target/debug/\n\"\"\"\n"
  },
  {
    "path": ".devcontainer/base-image/Dockerfile",
    "content": "# syntax=docker/dockerfile:1.4\n\nFROM mcr.microsoft.com/devcontainers/rust:2-1-bookworm\n\n# ========= Install cargo-tools for non-root user (vscode) =========\nUSER vscode\n\nARG cargo_crates\n\nRUN <<\"EOF\"\ncurl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash\ncargo binstall -y --locked ${cargo_crates}\nEOF\n\nUSER root\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// Dev Container for Rust, website, prqlc-js and prqlc-python\n{\n  \"image\": \"ghcr.io/prql/prql-devcontainer-base:latest\",\n  \"features\": {\n    \"ghcr.io/devcontainers/features/hugo:1\": {},\n    \"ghcr.io/devcontainers/features/python:1\": {},\n    \"ghcr.io/devcontainers/features/node:1\": {},\n    \"ghcr.io/eitsupi/devcontainer-features/go-task:1\": {},\n    \"ghcr.io/eitsupi/devcontainer-features/jq-likes:2\": {\n      \"yqVersion\": \"latest\"\n    },\n    \"ghcr.io/eitsupi/devcontainer-features/duckdb-cli:1\": {},\n    \"ghcr.io/rocker-org/devcontainer-features/apt-packages:1\": {\n      \"packages\": \"cmake,sqlite3\"\n    }\n  },\n  \"customizations\": {\n    \"vscode\": {\n      \"extensions\": [\n        // Keep in sync with Taskfile.yaml\n        \"prql-lang.prql-vscode\",\n        \"rust-lang.rust-analyzer\",\n        \"mitsuhiko.insta\",\n        \"esbenp.prettier-vscode\",\n        \"budparr.language-hugo-vscode\"\n      ]\n    }\n  },\n  \"mounts\": [\n    {\n      \"source\": \"devcontainer-cargo-cache-${devcontainerId}\",\n      \"target\": \"/usr/local/cargo/registry\",\n      \"type\": \"volume\"\n    },\n    {\n      \"source\": \"devcontainer-cargo-target-${devcontainerId}\",\n      \"target\": \"${containerWorkspaceFolder}/target\",\n      \"type\": \"volume\"\n    }\n  ],\n  \"postCreateCommand\": {\n    \"set-ownership\": \"sudo chown vscode target /usr/local/cargo/registry/\",\n    \"install-python-deps\": \"task install-maturin\",\n    // Disabling because of the issues in #3709\n    // \"install-python-deps\": \"task install-maturin && task install-pre-commit && pre-commit install-hooks\",\n    \"install-npm-dependencies\": \"task install-npm-dependencies\"\n  }\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Currently required for:\n# - Elixir tests at\n#   https://github.com/PRQL/prql/blob/5eb4063dbd36bdac07529797dd2cec8c55127263/.github/workflows/test-elixir.yaml#L31\n# - Previously for Readme tests, those those now should support either lf or\n#   crlf, at https://github.com/PRQL/prql/blob/6d4662b4e6e0f07dc3cd8eb5c19cc78fc199d0b2/web/book/tests/documentation/readme.rs#L9\n* text=auto eol=lf\n\n# Prevent files from cluttering `git grep` results\n*.min.js -diff\n*.min.js.map -diff\n*.min.css -diff\n*.min.css.map -diff\npackage-lock.json -diff\n# mdbook expects the file to be named highlight.js so we cannot rename it to .min.js\nhighlight.js -diff\n"
  },
  {
    "path": ".github/.codecov.yaml",
    "content": "comment: false\n\nignore:\n  - \"**/tests/**\"\n\ncoverage:\n  status:\n    project:\n      default:\n        removed_code_behavior: adjust_base\n        # This disables report a success/failure. That's not helpful on `main`\n        # and we get the success/failure from the patch status on PRs.\n        informational: true\n\n    patch:\n      default:\n        only_pulls: true\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nWe prefer keeping our rules as short as possible and filling the gaps with the\nmortar of human interaction: empathy.\n\nAll we ask of members of this project is this:\n\n- Please treat each other with respect and understanding.\n- Please respect our wish to not serve as a stage for disputes about fairness or\n  personal differences.\n\nIf you can agree to these conditions, your contributions are welcome. If you can\nnot, please don’t spoil it for the rest of us.\n\nTo report a violation of these rules, please email\n[prql@prql-lang.org](mailto:prql@prql-lang.org) or contact one of the\n[project maintainers](https://github.com/orgs/PRQL/people).\n\n---\n\n(based on <https://jan-krueger.net/mincoc/>)\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\nCheck out our\n[Contributing Docs](https://prql-lang.org/book/project/contributing/)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "name: 🐛 Bug report\ndescription: File a bug report to help us improve\nlabels: [bug]\nbody:\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What happened?\n      description: |\n        Thanks for reporting a bug! Feel free to add any initial context here.\n\n  - type: textarea\n    id: prql-input\n    attributes:\n      label: PRQL input\n      description: |\n        A minimal, self-contained example that demonstrates the issue.\n\n        This will be automatically formatted into code, so no need for markdown backticks.\n      render: elm\n    validations:\n      required: true\n\n  - type: textarea\n    id: output\n    attributes:\n      label: SQL output\n      description: |\n        The SQL that PRQL currently compiles to. Feel free to use the [playground](https://prql-lang.org/playground/) to generate the SQL.\n\n        This will be automatically formatted into code, so no need for markdown backticks.\n      render: SQL\n    validations:\n      required: true\n\n  - type: textarea\n    id: expected-output\n    attributes:\n      label: Expected SQL output\n\n      description:\n        Optional; no need to write out if it's obvious from the context\n      render: SQL\n\n  - type: checkboxes\n    id: mvce-checkboxes\n    attributes:\n      label: MVCE confirmation\n      description: |\n        Please confirm that the bug report is minimal and doesn't exist already:\n\n        - **Minimal example** — the example is as focused as reasonably possible to demonstrate the underlying issue in PRQL. For example, it's not possible to exclude any line and still observe the bug.\n\n        - **New issue** — a search of GitHub Issues suggests this is not a duplicate.\n      options:\n        - label: Minimal example\n        - label: New issue\n\n  - type: textarea\n    id: extra\n    attributes:\n      label: Anything else?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yaml",
    "content": "blank_issues_enabled: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/something_else.yaml",
    "content": "name: Something else\ndescription: Anything that's not a bug report\nbody:\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What's up?\n"
  },
  {
    "path": ".github/actionlint.yaml",
    "content": "# Custom runner labels not yet recognized by actionlint\nself-hosted-runner:\n  labels:\n    - macos-15-intel\n"
  },
  {
    "path": ".github/actions/build-prqlc/action.yaml",
    "content": "name: build-prqlc\ndescription: >\n  Build prqlc\n\n  Note that much of this is copy/pasted into build-prqlc-c, so changes here\n  should generally be copied into that file.\ninputs:\n  target:\n    description: Build target\n    required: true\n  profile:\n    description: Build profile option; `dev` or `release`.\n    required: true\n  features:\n    description: Features to enable\n    default: \"\"\noutputs:\n  artifact-name:\n    description: The name of the artifact\n    value: ${{ steps.echo-artifact-name.outputs.artifact-name }}\n\nruns:\n  using: composite\n  steps:\n    - run: rustup target add ${{ inputs.target }}\n      shell: bash\n\n    - run: ./.github/workflows/scripts/set_version.sh\n      shell: bash\n\n    - name: Compute Cargo.lock hash\n      shell: bash\n      run: |\n        if command -v sha256sum &> /dev/null; then\n          echo \"cargo_lock_hash=$(sha256sum Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n        else\n          echo \"cargo_lock_hash=$(shasum -a 256 Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n        fi\n\n    - uses: Swatinem/rust-cache@v2\n      with:\n        prefix-key: ${{ env.version }}-${{ env.cargo_lock_hash }}\n        # Share cache with `test-rust`, except for `musl` targets.\n        save-if:\n          ${{ (github.ref == 'refs/heads/main') && contains(inputs.target,\n          'musl') }}\n        shared-key: rust-${{ inputs.target }}\n\n    - if: runner.os == 'Linux'\n      shell: bash\n      run: |\n        sudo apt-get update\n        sudo apt-get install -y musl-tools\n\n    - if: runner.os == 'Windows' && inputs.profile == 'release'\n      shell: bash\n      run: echo 'RUSTFLAGS=-Ctarget-feature=+crt-static' >>\"$GITHUB_ENV\"\n\n    - if: inputs.target == 'aarch64-unknown-linux-musl'\n      shell: bash\n      run: |\n        sudo apt-get update\n        sudo apt-get install -y gcc-aarch64-linux-gnu\n        echo 'CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc' >>\"$GITHUB_ENV\"\n        echo 'CC=aarch64-linux-gnu-gcc' >>\"$GITHUB_ENV\"\n\n    - name: cargo build\n      uses: clechasseur/rs-cargo@v4\n      with:\n        command: build\n        # We previously had `--package=prqlc` for all, but this caches much\n        # worse than just building the whole workspace.\n        # https://github.com/PRQL/prql/issues/3098, so we just build the whole\n        # workspace — except for the `musl` target, which can't build the whole\n        # workspace. (This is overly complicated and would be great to simplify,\n        # even at the cost of slighly less efficiency.)\n        args:\n          --profile=${{ inputs.profile }} --locked --target=${{ inputs.target }}\n          --no-default-features --features=${{ inputs.features }} ${{\n          contains(inputs.target, 'musl') && '--package=prqlc' ||\n          '--all-targets' }}\n\n    - name: Create artifact for Linux and macOS\n      shell: bash\n      if: runner.os != 'Windows'\n      run: |\n        export ARTIFACT_NAME=\"prqlc-${{ github.ref_type == 'tag' && github.ref_name || 0 }}-${{ matrix.target }}.tar.gz\"\n        echo \"ARTIFACT_NAME=${ARTIFACT_NAME}\" >>\"$GITHUB_ENV\"\n        TEMP_DIR=$(mktemp -d)\n        cp prqlc/prqlc/README.md LICENSE \"${TEMP_DIR}/\"\n        cp -r target/${{ matrix.target }}/${{ inputs.profile == 'release' && 'release' || 'debug' }}/prqlc \"${TEMP_DIR}/\"\n        tar czf \"${ARTIFACT_NAME}\" -C \"$TEMP_DIR\" .\n\n    - name: Create artifact for Windows\n      shell: bash\n      if: runner.os == 'Windows'\n      run: |\n        export ARTIFACT_NAME=\"prqlc-${{ github.ref_type == 'tag' && github.ref_name || 0 }}-${{ matrix.target }}.zip\"\n        echo \"ARTIFACT_NAME=${ARTIFACT_NAME}\" >>\"$GITHUB_ENV\"\n        cd target/${{ matrix.target }}/${{ inputs.profile == 'release' && 'release' || 'debug' }}\n        cp ../../../prqlc/prqlc/README.md .\n        7z a \"../../../${ARTIFACT_NAME}\" prqlc.exe ../../../LICENSE README.md\n\n    - name: Upload prqlc\n      uses: actions/upload-artifact@v5\n      with:\n        name: prqlc-${{ inputs.target }}-${{ inputs.profile }}\n        path: ${{ env.ARTIFACT_NAME }}\n\n    - id: echo-artifact-name\n      shell: bash\n      run: echo \"artifact-name=${{ env.ARTIFACT_NAME }}\" >>\"$GITHUB_OUTPUT\"\n"
  },
  {
    "path": ".github/actions/build-prqlc-c/action.yaml",
    "content": "name: build-prqlc-c\ndescription: >\n  A version of `build-prqlc` for the C bindings.\n\n  Note that this is quite open to change, including names and which files we\n  package. Contributions and/or suggestions are welcome.\n\ninputs:\n  target:\n    description: Build target\n    required: true\n  profile:\n    description: Build profile option; `dev` or `release`.\n    required: true\n  features:\n    description: Features to enable\n    default: \"\"\noutputs:\n  artifact-name:\n    description: The name of the artifact\n    value: ${{ steps.echo-artifact-name.outputs.artifact-name }}\n\nruns:\n  using: composite\n  steps:\n    - run: rustup target add ${{ inputs.target }}\n      shell: bash\n\n    - run: ./.github/workflows/scripts/set_version.sh\n      shell: bash\n\n    - name: Compute Cargo.lock hash\n      shell: bash\n      run: |\n        if command -v sha256sum &> /dev/null; then\n          echo \"cargo_lock_hash=$(sha256sum Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n        else\n          echo \"cargo_lock_hash=$(shasum -a 256 Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n        fi\n\n    - uses: Swatinem/rust-cache@v2\n      with:\n        prefix-key: ${{ env.version }}-${{ env.cargo_lock_hash }}\n        # Share cache with `test-rust`, except for `musl` targets.\n        save-if:\n          ${{ (github.ref == 'refs/heads/main') && contains(inputs.target,\n          'musl') }}\n        shared-key: rust-${{ inputs.target }}\n\n    - if: runner.os == 'Linux'\n      shell: bash\n      run: |\n        sudo apt-get update\n        sudo apt-get install -y musl-tools\n\n    - if: runner.os == 'Windows' && inputs.profile == 'release'\n      shell: bash\n      run: echo 'RUSTFLAGS=-Ctarget-feature=+crt-static' >>\"$GITHUB_ENV\"\n\n    - if: inputs.target == 'aarch64-unknown-linux-musl'\n      shell: bash\n      run: |\n        sudo apt-get update\n        sudo apt-get install -y gcc-aarch64-linux-gnu\n        echo 'CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc' >>\"$GITHUB_ENV\"\n        echo 'CC=aarch64-linux-gnu-gcc' >>\"$GITHUB_ENV\"\n\n    - name: cargo build\n      uses: clechasseur/rs-cargo@v4\n      with:\n        command: build\n        args:\n          --profile=${{ inputs.profile }} --locked --target=${{ inputs.target }}\n          --no-default-features --features=${{ inputs.features }} ${{\n          contains(inputs.target, 'musl') && '--package=prqlc-c' ||\n          '--all-targets' }}\n\n    - name: Create artifact for Linux and macOS\n      shell: bash\n      if: runner.os != 'Windows'\n      run: |\n        export ARTIFACT_NAME=\"prqlc_c-${{ github.ref_type == 'tag' && github.ref_name || 0 }}-${{ matrix.target }}.tar.gz\"\n        echo \"ARTIFACT_NAME=${ARTIFACT_NAME}\" >>\"$GITHUB_ENV\"\n        cd target/${{ matrix.target }}/${{ inputs.profile == 'release' && 'release' || 'debug' }}\n        ls -al\n        tar czf \"../../../${ARTIFACT_NAME}\" *prqlc_c*\n\n    - name: Create artifact for Windows\n      shell: bash\n      if: runner.os == 'Windows'\n      run: |\n        export ARTIFACT_NAME=\"prqlc_c-${{ github.ref_type == 'tag' && github.ref_name || 0 }}-${{ matrix.target }}.zip\"\n        echo \"ARTIFACT_NAME=${ARTIFACT_NAME}\" >>\"$GITHUB_ENV\"\n        cd target/${{ matrix.target }}/${{ inputs.profile == 'release' && 'release' || 'debug' }}\n        ls -al\n        7z a \"../../../${ARTIFACT_NAME}\" *prqlc_c*\n\n    - name: Upload prqlc-c\n      uses: actions/upload-artifact@v5\n      with:\n        name: prqlc_c-${{ inputs.target }}-${{ inputs.profile }}\n        path: ${{ env.ARTIFACT_NAME }}\n\n    - id: echo-artifact-name\n      shell: bash\n      run: echo \"artifact-name=${{ env.ARTIFACT_NAME }}\" >>\"$GITHUB_OUTPUT\"\n"
  },
  {
    "path": ".github/actions/build-python/action.yaml",
    "content": "name: build-wheel\ndescription: \"Use maturin to build python dists.\"\ninputs:\n  target:\n    description:\n      Maturin build target, or 'source' for source distribution. Currently only\n      used for Linux, otherwise defaults to the platform.\n    required: true\n  profile:\n    description: Build profile option; `dev` or `release`.\n    required: true\n  package:\n    description: Package name\n    required: true\n\nruns:\n  using: composite\n  steps:\n    - id: package-path\n      run:\n        echo \"package_path=$(cargo metadata --no-deps --format-version 1 | jq -r\n        --arg package_name ${{ inputs.package }} '.packages[] | select(.name ==\n        $package_name) | .manifest_path')\" >>\"$GITHUB_OUTPUT\"\n      shell: bash\n    # There's no benefit from caching here, because the maturin action uses a container.\n    - uses: PyO3/maturin-action@v1\n      if: inputs.target == 'source'\n      with:\n        command: sdist\n        args: -o target/python -m ${{steps.package-path.outputs.package_path}}\n    - uses: PyO3/maturin-action@v1\n      if: runner.os == 'Linux' && inputs.target != 'source'\n      with:\n        target: ${{ inputs.target }}\n        manylinux: auto\n        command: build\n        args:\n          --profile=${{ inputs.profile }} -o target/python -m\n          ${{steps.package-path.outputs.package_path}}\n    - uses: PyO3/maturin-action@v1\n      if: runner.os == 'Windows' && inputs.target != 'source'\n      with:\n        command: build\n        args:\n          --profile=${{ inputs.profile }} -o target/python -m\n          ${{steps.package-path.outputs.package_path}}\n    - uses: PyO3/maturin-action@v1\n      if: runner.os == 'macOS' && inputs.target != 'source'\n      with:\n        command: build\n        # We override the target with `universal2-apple-darwin`. Probably we\n        # should use the target from the input or at least ensure it matches.\n        args:\n          --profile=${{ inputs.profile }} -o target/python --target\n          universal2-apple-darwin -m\n          ${{steps.package-path.outputs.package_path}}\n    - name: Upload wheels\n      uses: actions/upload-artifact@v5\n      with:\n        name:\n          # We could avoid the OS here if we handled the target more explicitly.\n          ${{ inputs.package }}-${{ runner.os }}-${{ inputs.target }}-${{\n          inputs.profile }}\n        path: target/python\n"
  },
  {
    "path": ".github/actions/time-compilation/action.yaml",
    "content": "name: Time Compilation\ndescription: Time the cargo compilation, outputting an HTML file.\n\ninputs:\n  use_cache:\n    required: true\n    description: Whether to use the cache of dependencies\n\nruns:\n  using: composite\n  steps:\n    - run: ./.github/workflows/scripts/set_version.sh\n      shell: bash\n    - name: 💰 Cache\n      id: cache\n      uses: Swatinem/rust-cache@v2\n      with:\n        prefix-key: ${{ env.version }}-${{ hashFiles('./Cargo.lock') }}\n        save-if: ${{ github.ref == 'refs/heads/main' }}\n      # 'true' seems to require quotes (and using a bare `inputs.use_cache`\n      # doesn't work); I'm really not sure why. There are some issues on the\n      # interwebs around this, but I couldn't find one that explained it.\n      if: inputs.use_cache == 'true'\n    - name: Remove cached results\n      shell: bash\n      run: rm -rf target/cargo-timings\n    - name: 🏭 Compile\n      uses: clechasseur/rs-cargo@v4\n      with:\n        command: build\n        args: --timings --all-targets\n    - uses: actions/upload-artifact@v5\n      with:\n        name:\n          cargo-timing-${{ inputs.use_cache == 'true' && 'cache' || 'no_cache'\n          }}.html\n        path: target/cargo-timings/cargo-timing-*.html\n        if-no-files-found: error\n        # Only upload if a) we got a cache hit or b) we didn't want to use the cache anyway\n      if: steps.cache.outputs.cache-hit == 'true' || inputs.use_cache == 'false'\n"
  },
  {
    "path": ".github/dependabot.yaml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: cargo\n    directory: \"/\"\n    schedule:\n      # Running daily means the GHA cache will be cycling much faster, and we're\n      # not in a great rush. So trying weekly, and we can iterate.\n      interval: weekly\n    commit-message:\n      prefix: \"chore: \"\n    # Bump all patch versions of rust dependencies as a single PR\n    groups:\n      patch:\n        update-types:\n          - patch\n    # We exclude labels throughout because of https://github.com/dependabot/dependabot-core/issues/7645#issuecomment-1986212847\n    labels: []\n\n  - package-ecosystem: \"npm\"\n    directories:\n      - \"/prqlc/bindings/js\"\n      - \"/web/playground\"\n    schedule:\n      interval: daily\n    ignore:\n      - dependency-name: \"*\"\n        update-types:\n          - version-update:semver-patch\n    commit-message:\n      prefix: \"chore: \"\n    labels: []\n\n  - package-ecosystem: docker\n    directory: .devcontainer/base-image\n    schedule:\n      interval: daily\n    commit-message:\n      prefix: \"chore: \"\n    labels: []\n\n  - package-ecosystem: \"github-actions\"\n    directories:\n      - \"/\"\n      - \".github/actions/build-python\"\n      - \".github/actions/build-prqlc\"\n      - \".github/actions/build-prqlc-c\"\n      - \".github/actions/time-compilation\"\n    commit-message:\n      prefix: \"chore: \"\n    schedule:\n      interval: daily\n    labels: []\n\n  - package-ecosystem: uv\n    directory: \"prqlc/bindings/prqlc-python\"\n    schedule:\n      interval: daily\n    commit-message:\n      prefix: \"chore: \"\n    ignore:\n      - dependency-name: \"*\"\n        update-types:\n          - version-update:semver-patch\n    labels: []\n\n  - package-ecosystem: \"devcontainers\"\n    directory: \"/\"\n    schedule:\n      interval: daily\n    labels: []\n\n  - package-ecosystem: \"mix\"\n    directory: \"prqlc/bindings/elixir\"\n    schedule:\n      interval: daily\n    commit-message:\n      prefix: \"chore: \"\n    labels: []\n"
  },
  {
    "path": ".github/nightly-failure.md",
    "content": "---\ntitle: Nightly tests failed\nlabels: github_actions\n---\n\nNightly tests [failed on {{ date | date('YYYY-MM-DD') }}]({{ env.LINK }})\n"
  },
  {
    "path": ".github/workflows/README.md",
    "content": "# GitHub Workflows\n\nSee\n[our development docs](https://prql-lang.org/book/project/contributing/development.html)\nfor docs & discussion.\n"
  },
  {
    "path": ".github/workflows/build-devcontainer.yaml",
    "content": "name: build-devcontainer\n\n# Multi-platform build for devcontainer base image\non:\n  workflow_call:\n    inputs:\n      push:\n        type: boolean\n        default: false\n  workflow_dispatch:\n    inputs:\n      push:\n        type: boolean\n        default: false\n\nenv:\n  IMAGE_NAME: ghcr.io/prql/prql-devcontainer-base\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - platform: linux/amd64\n            platform_name: amd64\n            runner: ubuntu-24.04\n          - platform: linux/arm64\n            platform_name: arm64\n            runner: ubuntu-24.04-arm\n    runs-on: ${{ matrix.runner }}\n    timeout-minutes: 240\n    steps:\n      - uses: actions/checkout@v5\n      - uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - uses: docker/setup-buildx-action@v3\n\n      - name: Prep build args\n        run:\n          echo \"cargo_crates=$(yq -r '.vars.cargo_crates' Taskfile.yaml)\" >>\n          \"$GITHUB_ENV\"\n\n      - name: Build and push by digest\n        id: build\n        uses: docker/build-push-action@v6\n        timeout-minutes: 240\n        with:\n          context: .devcontainer/base-image\n          build-args: cargo_crates=${{ env.cargo_crates }}\n          platforms: ${{ matrix.platform }}\n          outputs:\n            type=image,name=${{ env.IMAGE_NAME\n            }},push-by-digest=true,name-canonical=true,push=${{ inputs.push }}\n          cache-from: type=gha,scope=${{ matrix.platform }}\n          cache-to: type=gha,mode=max,scope=${{ matrix.platform }}\n\n      # Smoke test: build local image and verify tools work\n      - name: Build local image for smoke test\n        uses: docker/build-push-action@v6\n        with:\n          context: .devcontainer/base-image\n          build-args: cargo_crates=${{ env.cargo_crates }}\n          load: true\n          tags: devcontainer-test:latest\n          cache-from: type=gha,scope=${{ matrix.platform }}\n\n      - name: Smoke test - run tests and build book\n        run: |\n          docker run --rm -v \"${{ github.workspace }}:/workspace\" -w /workspace \\\n            devcontainer-test:latest bash -c \"\n              set -euxo pipefail\n              cargo test\n              mdbook build web/book/\n            \"\n\n      - name: Export digest\n        if: inputs.push\n        run: |\n          mkdir -p /tmp/digests\n          digest=\"${{ steps.build.outputs.digest }}\"\n          touch \"/tmp/digests/${digest#sha256:}\"\n\n      - name: Upload digest\n        if: inputs.push\n        uses: actions/upload-artifact@v5\n        with:\n          name: digests-${{ matrix.platform_name }}\n          path: /tmp/digests/*\n          if-no-files-found: error\n          retention-days: 1\n\n  merge:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 30\n    if: inputs.push\n    needs: build\n    steps:\n      - name: Download digests\n        uses: actions/download-artifact@v6\n        with:\n          path: /tmp/digests\n          pattern: digests-*\n          merge-multiple: true\n\n      - name: Validate digests\n        run: |\n          digest_count=$(find /tmp/digests -type f | wc -l)\n          if [ \"$digest_count\" -ne 2 ]; then\n            echo \"Error: Expected 2 digests (amd64 + arm64), found $digest_count\"\n            ls -la /tmp/digests\n            exit 1\n          fi\n\n      - uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - uses: docker/setup-buildx-action@v3\n      - uses: docker/metadata-action@v5\n        id: meta\n        with:\n          images: ${{ env.IMAGE_NAME }}\n          tags: type=raw,latest\n\n      - name: Create manifest list and push\n        working-directory: /tmp/digests\n        run: |\n          tags=$(jq -cr '.tags | map(\"-t \" + .) | join(\" \")' <<< \"$DOCKER_METADATA_OUTPUT_JSON\")\n          digests=$(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *)\n          # shellcheck disable=SC2086\n          docker buildx imagetools create $tags $digests\n\n      - name: Verify multi-platform manifest\n        run: |\n          docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}\n          platforms=$(docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} --raw | \\\n            jq -r '.manifests[] | select(.platform.os != \"unknown\") | .platform | \"\\(.os)/\\(.architecture)\"' | sort)\n          expected=\"linux/amd64\n          linux/arm64\"\n          if [ \"$platforms\" != \"$expected\" ]; then\n            echo \"Error: Expected platforms not found\"\n            echo \"Expected: $expected\"\n            echo \"Found: $platforms\"\n            exit 1\n          fi\n          echo \"✓ Multi-platform manifest verified: amd64 + arm64\"\n"
  },
  {
    "path": ".github/workflows/build-web.yaml",
    "content": "name: build-web\n\non:\n  workflow_call:\n  workflow_dispatch:\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  build-web:\n    runs-on: ubuntu-24.04\n\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n\n      # Website requires Hugo\n      - name: Setup Hugo\n        uses: peaceiris/actions-hugo@v3.0.0\n\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: mdbook\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: mdbook-footnote\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: wasm-pack\n\n      - name: Setup Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"20.x\"\n          cache: \"npm\"\n          cache-dependency-path: \"**/package-lock.json\"\n\n      - run: ./.github/workflows/scripts/set_version.sh\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: ${{ env.version }}-${{ hashFiles('./Cargo.lock') }}\n          shared-key: web\n          save-if:\n            ${{ github.ref == 'refs/heads/web' || github.ref ==\n            'refs/heads/main' }}\n\n      - uses: go-task/setup-task@v1\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 🕷️ Build web\n        run: task web:build\n\n      - uses: actions/upload-pages-artifact@v4.0.0\n        with:\n          path: web/website/public/\n\n  build-codemirror-demo:\n    runs-on: ubuntu-24.04\n\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - name: 🧅 Setup Bun\n        uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n      - name: Install CodeMirror demo dependencies\n        working-directory: web/prql-codemirror-demo/\n        run: bun install\n      - name: Install Lezer dependencies\n        working-directory: grammars/prql-lezer/\n        run: bun install\n      - name: Build Lezer grammar\n        working-directory: grammars/prql-lezer/\n        run: bun run build\n      - name: Copy Lezer grammar into demo\n        working-directory: web/prql-codemirror-demo/\n        run: |\n          mkdir src/lang-prql/prql-lezer\n          cp ../../grammars/prql-lezer/dist/* src/lang-prql/prql-lezer/\n      - name: Build CodeMirror demo\n        working-directory: web/prql-codemirror-demo/\n        run: bun run build\n"
  },
  {
    "path": ".github/workflows/claude.yaml",
    "content": "name: Claude Code\n\non:\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  claude:\n    # Only run when comment contains @claude\n    if: |\n      (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||\n      (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude'))\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write # Allow pushing commits\n      pull-requests: write # Allow commenting and updating PRs\n      issues: write # Allow commenting on issues\n      id-token: write\n      actions: read # Required for Claude to read CI results on PRs\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n        with:\n          # Fix: Checkout PR branch for issue_comment events instead of default branch\n          ref:\n            ${{ github.event.issue.pull_request && format('refs/pull/{0}/head',\n            github.event.issue.number) || github.ref }}\n          fetch-depth: 0 # Get more history for better context\n          fetch-tags: true\n\n      - name: 🔧 Configure git for Claude\n        run: |\n          git config --global user.name \"Claude Code\"\n          git config --global user.email \"claude@anthropic.com\"\n\n      # Install essential tools for the PRQL project\n      - name: 🔧 Setup Task\n        uses: go-task/setup-task@v1\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n\n      # Rust is pre-installed on ubuntu-latest, but we ensure consistent toolchain\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-insta\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-nextest\n\n      - run: ./.github/workflows/scripts/set_version.sh\n        shell: bash\n\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: ${{ hashFiles('./Cargo.lock') }}\n          shared-key: rust-x86_64-unknown-linux-gnu\n          save-if: false\n\n      - name: 🤖 Run Claude Code\n        id: claude\n        uses: anthropics/claude-code-action@v1\n        with:\n          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}\n          show_full_output: true\n\n          # This is an optional setting that allows Claude to read CI results on PRs\n          additional_permissions: |\n            actions: read\n\n          # Model, tools, and CI behavior. System prompt must be single-line\n          # with \\n escapes (YAML multiline breaks argument parsing).\n          claude_args: |\n            --model opus\n            --allowedTools Bash,Edit,Read,Write,Glob,Grep,WebSearch,WebFetch\n            --system-prompt \"You are helping with the PRQL project in a GitHub Actions environment.\\n\\nFollow the project guidelines in CLAUDE.md.\\nAfter making changes, ensure tests pass with 'task test-all' and lints with 'task lint'.\\n\\nFor CI failure diagnosis:\\n- Use 'gh run list' and 'gh run view' to check CI status\\n- Look for specific error messages in failing jobs\\n- Check the test output for detailed failure information\\n- When fixing issues, verify changes with the appropriate test commands\\n- Once local test commands work, push to the PR\\n- Then: iterate between monitoring CI, fixing any remaining issues, monitoring CI\\n- Don't return until we're confident that we've done everything we can, including monitoring CI to completion and fixing any failures\"\n"
  },
  {
    "path": ".github/workflows/lint-megalinter.yaml",
    "content": "# MegaLinter GitHub Action configuration file\n# More info at https://megalinter.io\nname: lint-megalinter\n\non:\n  workflow_call:\n  workflow_dispatch:\n\n# Comment env block if you do not want to apply fixes\nenv:\n  # Apply linter fixes configuration\n  #\n  # When active, APPLY_FIXES must also be defined as environment variable\n  # (in github/workflows/mega-linter.yml or other CI tool)\n  # PRQL-change — edited out\n  # APPLY_FIXES: all\n\n  # Decide which event triggers application of fixes in a commit or a PR\n  # (pull_request, push, all)\n  APPLY_FIXES_EVENT: pull_request\n\n  # If APPLY_FIXES is used, defines if the fixes are directly committed (commit)\n  # or posted in a PR (pull_request)\n  APPLY_FIXES_MODE: commit\n\nconcurrency:\n  group: ${{ github.ref }}-${{ github.workflow }}\n  cancel-in-progress: true\n\njobs:\n  megalinter:\n    name: MegaLinter\n    runs-on: ubuntu-24.04\n\n    # Give the default GITHUB_TOKEN write permission to commit and push, comment\n    # issues & post new PR; remove the ones you do not need\n    permissions:\n      contents: write\n      issues: write\n      pull-requests: write\n\n    steps:\n      # Git Checkout\n      - name: Checkout Code\n        uses: actions/checkout@v5\n        with:\n          token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}\n\n          # If you use VALIDATE_ALL_CODEBASE = true, you can remove this line to\n          # improve performance\n          fetch-depth: 0\n\n      # MegaLinter\n      - name: MegaLinter\n\n        # You can override MegaLinter flavor used to have faster performances\n        # More info at https://megalinter.io/flavors/\n        uses: oxsecurity/megalinter@v9.1.0\n\n        id: ml\n\n        # All available variables are described in documentation\n        # https://megalinter.io/configuration/\n        env:\n          VALIDATE_ALL_CODEBASE: true\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n          # ADD YOUR CUSTOM ENV VARIABLES HERE OR DEFINE THEM IN A FILE\n          # .mega-linter.yml AT THE ROOT OF YOUR REPOSITORY\n\n          # Uncomment to disable copy-paste and spell checks\n          # DISABLE: COPYPASTE,SPELL\n\n      # Upload MegaLinter artifacts\n      - name: Archive production artifacts\n        uses: actions/upload-artifact@v5\n        if: success() || failure()\n        with:\n          name: MegaLinter reports\n          path: |\n            megalinter-reports\n            mega-linter.log\n\n      # Set APPLY_FIXES_IF var for use in future steps\n      - name: Set APPLY_FIXES_IF var\n        run: |\n          printf 'APPLY_FIXES_IF=%s\\n' \"${{\n            steps.ml.outputs.has_updated_sources == 1 &&\n            (\n              env.APPLY_FIXES_EVENT == 'all' ||\n              env.APPLY_FIXES_EVENT == github.event_name\n            ) &&\n            (\n              github.event_name == 'push' ||\n              github.event.pull_request.head.repo.full_name == github.repository\n            )\n          }}\" >> \"${GITHUB_ENV}\"\n\n      # Set APPLY_FIXES_IF_* vars for use in future steps\n      - name: Set APPLY_FIXES_IF_* vars\n        run: |\n          printf 'APPLY_FIXES_IF_PR=%s\\n' \"${{\n            env.APPLY_FIXES_IF == 'true' &&\n            env.APPLY_FIXES_MODE == 'pull_request'\n          }}\" >> \"${GITHUB_ENV}\"\n          printf 'APPLY_FIXES_IF_COMMIT=%s\\n' \"${{\n            env.APPLY_FIXES_IF == 'true' &&\n            env.APPLY_FIXES_MODE == 'commit' &&\n            (!contains(fromJSON('[\"refs/heads/main\", \"refs/heads/master\"]'), github.ref))\n          }}\" >> \"${GITHUB_ENV}\"\n\n      # Create pull request if applicable\n      # (for now works only on PR from same repository, not from forks)\n      - name: Create Pull Request with applied fixes\n        uses: peter-evans/create-pull-request@v7\n        id: cpr\n        if: env.APPLY_FIXES_IF_PR == 'true'\n        with:\n          token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}\n          commit-message: \"[MegaLinter] Apply linters automatic fixes\"\n          title: \"[MegaLinter] Apply linters automatic fixes\"\n          labels: bot\n\n      - name: Create PR output\n        if: env.APPLY_FIXES_IF_PR == 'true'\n        run: |\n          echo \"PR Number - ${{ steps.cpr.outputs.pull-request-number }}\"\n          echo \"PR URL - ${{ steps.cpr.outputs.pull-request-url }}\"\n\n      # Push new commit if applicable\n      # (for now works only on PR from same repository, not from forks)\n      - name: Prepare commit\n        if: env.APPLY_FIXES_IF_COMMIT == 'true'\n        run: sudo chown -Rc $UID .git/\n\n      - name: Commit and push applied linter fixes\n        uses: stefanzweifel/git-auto-commit-action@v7\n        if: env.APPLY_FIXES_IF_COMMIT == 'true'\n        with:\n          branch: >-\n            ${{\n              github.event.pull_request.head.ref ||\n              github.head_ref ||\n              github.ref\n            }}\n          commit_message: \"[MegaLinter] Apply linters fixes\"\n          commit_user_name: megalinter-bot\n          commit_user_email: nicolas.vuillamy@ox.security\n"
  },
  {
    "path": ".github/workflows/nightly.yaml",
    "content": "# A workflow containing jobs we run only on nightly. This is called by\n# `tests.yaml` on a schedule and on request with a `pr-nightly` label. The\n# workflow encapsulates lots of jobs rather than listing them all in `tests.yaml`\n# and conditioning each on `nightly` (wouldn't be terrible but less modular,).\n\nname: nightly\n\non:\n  workflow_call:\n  workflow_dispatch:\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  cargo-audit:\n    runs-on: ubuntu-24.04\n    # We can't read PRQL repo security events on forks, which causes this to\n    # incorrectly fail (\n    # https://github.com/PRQL/prql/actions/runs/5718693342/job/15495030808?pr=3195#step:3:28\n    # ). So we disable. If we wanted to run checks on PRs, we could move this to\n    # `pull-request-target`.\n    #\n    # Would be better if we could only run when we know we have permissions. But\n    # this will do...\n\n    # 2024-12-09 — this is failing on normal PRs despite clearing the advisories\n    # in GitHub, and the action is archived. We have the GitHub security audits,\n    # `prqlc` doesn't cross any trust boundaries, so disabling for the moment.\n    # We can re-enable if a supported action is available.\n    if:\n      ${{ github.repository_owner == 'prql' &&\n      !github.event.pull_request.head.repo.fork && 'run' == 'no' }}\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n      issues: write\n      checks: write\n\n    steps:\n      - uses: actions/checkout@v5\n      - uses: rustsec/audit-check@v2.0.0\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n\n  cargo-bench:\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v5\n      - uses: clechasseur/rs-cargo@v4\n        with:\n          command: bench\n          # GH Actions is fairly noisy, so the precise details don't matter that\n          # much. We do want to check the benchmarks run, and possibly we can\n          # use this to identify and big changes in performance.\n          args: -- --warm-up-time=0.3 --measurement-time=1\n\n  time-compilation:\n    runs-on: ubuntu-24.04\n    strategy:\n      matrix:\n        use_cache: [true, false]\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n      - uses: ./.github/actions/time-compilation\n        with:\n          use_cache: ${{ matrix.use_cache }}\n    # We need consistent env vars across all workflows for the cache to work\n    env:\n      CARGO_TERM_COLOR: always\n      CLICOLOR_FORCE: 1\n      RUSTFLAGS: \"-C debuginfo=0\"\n      RUSTDOCFLAGS: \"-Dwarnings\"\n\n  check-unused-dependencies:\n    runs-on: ubuntu-24.04\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - run: rustup override set nightly-2025-11-10\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-udeps\n      # Once with all targets, once without, to find anything that should be in\n      # `dev` but is more general.\n      - uses: clechasseur/rs-cargo@v4\n        with:\n          command: udeps\n          args: --all-targets\n      - uses: clechasseur/rs-cargo@v4\n        with:\n          command: udeps\n\n  # We now use the devcontainer. TODO: is it possible to have a similar test for\n  # that? Or that would require VSCode to install the dependencies?\n\n  # test-docker:\n  #   # We only test the build in `test-all`; this also runs tests.\n  #   runs-on: ubuntu-24.04\n  #   steps:\n  #     - name: 📂 Checkout code\n  #       uses: actions/checkout@v5\n\n  #     - uses: docker/setup-buildx-action@v2\n\n  #     - name: Build\n  #       uses: docker/build-push-action@v4\n  #       with:\n  #         tags: prql:latest\n  #         # Use the GHA cache\n  #         load: true\n  #         cache-from: type=gha\n  #         cache-to: type=gha,mode=max\n\n  #     # https://aschmelyun.com/blog/using-docker-run-inside-of-github-actions/\n  #     - name: Test\n  #       uses: addnab/docker-run-action@v3\n  #       with:\n  #         image: prql:latest\n  #         options: -v ${{ github.workspace }}/:/src\n  #         run: task test-rust\n\n  code-ql:\n    # Currently almost the default code-ql config\n    runs-on: ubuntu-24.04\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [\"javascript\", \"python\"]\n        # We could add java, but it require a custom build step and we have very little java...\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v5\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v4\n        with:\n          languages: ${{ matrix.language }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n          # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n          # queries: security-extended,security-and-quality\n\n      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n      # If this step fails, then you should remove it and run the build manually (see below)\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v4\n\n      # ℹ️ Command-line programs to run using the OS shell.\n      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n\n      #   If the Autobuild fails above, remove it and uncomment the following three lines.\n      #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.\n\n      # - run: |\n      #   echo \"Run, Build Application using script\"\n      #   ./location_of_script_within_repo/buildscript.sh\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v4\n\n  nightly-release:\n    # Test release workflow\n    uses: ./.github/workflows/release.yaml\n"
  },
  {
    "path": ".github/workflows/publish-web.yaml",
    "content": "name: publish-web\non:\n  push:\n    branches:\n      - web\n  # Even though releases push to `web` branch, that doesn't cause this workflow\n  # to run, because GHA can't start workflows itself. So we also run on\n  # releases.\n  release:\n    types: [released]\n  # Called by pull-request when specifically requested\n  workflow_call:\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  build-web:\n    uses: ./.github/workflows/build-web.yaml\n\n  deploy-web:\n    needs: build-web\n    runs-on: ubuntu-24.04\n\n    # Don't attempt to publish if on a fork or on a PR running on upstream.\n    if:\n      ${{ github.repository_owner == 'prql' &&\n      !github.event.pull_request.head.repo.fork }}\n\n    permissions:\n      pages: write\n      id-token: write\n\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n\n    steps:\n      - name: Setup Pages\n        id: pages\n        uses: actions/configure-pages@v5\n\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4.0.5\n"
  },
  {
    "path": ".github/workflows/pull-request-target.yaml",
    "content": "name: pull-request-target\n\non:\n  pull_request_target:\n    types: [opened, edited, synchronize, labeled, closed]\n    branches:\n      - \"*\"\n\nconcurrency:\n  # Generally we use `github.ref`, but in pull_request_target, that's always `main`.\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}\n  cancel-in-progress: true\n\njobs:\n  main:\n    name: Validate PR title\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: amannn/action-semantic-pull-request@v6\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          requireScope: false\n          # The standard ones plus\n          # - \"internal\" for code quality / ergonomics improvements\n          # - \"devops\" for developer ergonomics\n          # - \"web\" for playground / website (but not docs)\n          # - \"refine\" for tiny changes\n          types: |\n            feat\n            fix\n            docs\n            style\n            refactor\n            perf\n            test\n            build\n            ci\n            chore\n            revert\n\n            internal\n            devops\n            web\n            refine\n\n  backport:\n    # Backport to `web` branch on `pr-backport-web`\n    name: Backport to `web` branch\n    runs-on: ubuntu-24.04\n    # Confirm that it's merged and has a label to ensure nothing is backported without oversight\n    if: |\n      github.event.pull_request.merged\n      && (\n        github.event.action == 'closed'\n        || (\n          github.event.action == 'labeled'\n          && contains(github.event.label.name, 'pr-backport-web')\n        )\n      )\n    steps:\n      - uses: tibdex/backport@v2\n        with:\n          # This is a personal access token from the @prql-bot\n          github_token: ${{ secrets.PRQL_BOT_GITHUB_TOKEN }}\n          # Docs are at https://github.com/tibdex/backport/blob/main/action.yml\n          # We only use `web` atm\n          label_pattern: \"^pr-backport-(?<base>([^ ]+))$\"\n          title_template: \"chore: Backport #<%= number%> to `web`\"\n\n  automerge:\n    runs-on: ubuntu-24.04\n\n    permissions:\n      pull-requests: write\n      contents: write\n\n    # Check it's not coming from a fork — both to save running and\n    # in case someone spoofed an `actor` name.\n    if: ${{ !github.event.pull_request.head.repo.fork }}\n\n    steps:\n      # Get number of commits in the PR\n      - id: commit-count\n        run: >\n          echo \"commit-count=$(curl -s -H 'Authorization: token ${{ github.token\n          }}' https://api.github.com/repos/prql/prql/pulls/${{\n          github.event.pull_request.number }}/commits | jq 'length')\"\n          >>\"$GITHUB_OUTPUT\"\n      - if:\n          # - It's dependabot\n          # - or there's only one commit — so nothing has made further changes and\n          #   - or it's prql-bot\n          #   - or it's a pre-commit-ci update\n          github.actor == 'dependabot[bot]' ||\n          (steps.commit-count.outputs.commit-count == '1' && (github.actor ==\n          'prql-bot' || github.actor == 'pre-commit-ci[bot]'))\n        run:\n          gh pr merge --auto --squash --delete-branch ${{\n          github.event.pull_request.html_url }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.PRQL_BOT_GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "# This workflow runs on tags / releases. It also runs on nightly builds without\n# publishing anything, in order to test as much of the build works as possible.\n#\n# We indicate whether it should publish, vs. just build, by checking whether\n# `github.event_name == 'release'` . (An alternative would be to have an input\n# which is passed in by the calling workflow.)\n\nname: release\non:\n  release:\n    types: [released]\n  workflow_call:\n  workflow_dispatch:\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  brew-dispatcher:\n    name: Release on homebrew-prql\n    runs-on: ubuntu-24.04\n    if: github.event_name == 'release'\n    steps:\n      - uses: actions/github-script@v8\n        with:\n          github-token: ${{ secrets.PRQL_BOT_GITHUB_TOKEN }}\n          script: |\n            await github.rest.actions.createWorkflowDispatch({\n              owner: 'prql',\n              repo: 'homebrew-prql',\n              workflow_id: 'update.yaml',\n              ref: 'main',\n              inputs: {\n              version: '${{ github.ref }}',\n              URL: 'https://github.com/PRQL/prql/archive/${{ github.ref }}.tar.gz'\n              }\n            })\n\n  build-prqlc:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - os: ubuntu-24.04\n            target: x86_64-unknown-linux-musl\n          - os: ubuntu-24.04\n            target: aarch64-unknown-linux-musl\n          - os: macos-15\n            target: aarch64-apple-darwin\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n          # Intel macOS build\n          - os: macos-15-intel\n            target: x86_64-apple-darwin\n            features: default,test-dbs\n    permissions:\n      contents: write\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: ./.github/actions/build-prqlc\n        id: build-artifact\n        with:\n          target: ${{ matrix.target }}\n          profile: release\n          features: cli\n      - name: Upload release artifact\n        if: github.event_name == 'release'\n        uses: softprops/action-gh-release@v2\n        with:\n          append_body: true\n          files: ${{ steps.build-artifact.outputs.artifact-name }}\n      - name: test the CLI works\n        # TODO: Add for Windows too (but will require unzipping rather than\n        # un-taring)\n        #\n        # Currently filtering by x86, since that's the same as those not\n        # cross-compiled. But we'll have to refine this in the future.\n        if:\n          ${{ contains(matrix.target, 'x86') && matrix.target !=\n          'x86_64-pc-windows-msvc' }}\n        run: |\n          # `prqlc` is an existing path at the root\n          mkdir -p temp_path\n          tar xzf ${{ steps.build-artifact.outputs.artifact-name }} -C temp_path\n          ./temp_path/prqlc --help\n\n  build-prqlc-c:\n    # Mostly a copy/paste of `build-prqlc`.\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - os: ubuntu-24.04\n            target: x86_64-unknown-linux-musl\n          - os: ubuntu-24.04\n            target: aarch64-unknown-linux-musl\n          - os: macos-15\n            target: aarch64-apple-darwin\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n    permissions:\n      contents: write\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: ./.github/actions/build-prqlc-c\n        id: build-artifact\n        with:\n          target: ${{ matrix.target }}\n          profile: release\n      - name: Upload release artifact\n        if: github.event_name == 'release'\n        uses: softprops/action-gh-release@v2\n        with:\n          append_body: true\n          files: ${{ steps.build-artifact.outputs.artifact-name }}\n\n  publish-winget:\n    runs-on: ubuntu-24.04\n    needs: build-prqlc\n    if: github.event_name == 'release'\n    steps:\n      - name: publish\n        uses: vedantmgoyal2009/winget-releaser@v2\n        with:\n          identifier: PRQL.prqlc\n          version: ${{ github.ref_name }}\n          installers-regex: '^prqlc-.*-windows-.*\\.zip$'\n          token: ${{ secrets.PRQL_BOT_GITHUB_TOKEN }}\n          fork-user: prql-bot\n\n  build-deb-package:\n    runs-on: ubuntu-24.04\n    strategy:\n      fail-fast: false\n      matrix:\n        target:\n          - x86_64-unknown-linux-musl\n          - aarch64-unknown-linux-musl\n    needs: build-prqlc\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/download-artifact@v6\n        with:\n          name: prqlc-${{ matrix.target }}-release\n      - name: Copy files into .deb package\n        run: |\n          tar -xf prqlc-*.tar.gz\n          mkdir -p .debpkg/usr/bin\n          mv prqlc .debpkg/usr/bin/prqlc\n          chmod +x .debpkg/usr/bin/prqlc\n      - name: Set arch variable\n        run: |\n          if [ \"${{ matrix.target }}\" == \"aarch64-unknown-linux-musl\" ]; then\n            echo \"package_arch=arm64\" >> \"$GITHUB_ENV\"\n          elif [ \"${{ matrix.target }}\" == \"x86_64-unknown-linux-musl\" ]; then\n            echo \"package_arch=amd64\" >> \"$GITHUB_ENV\"\n          else\n            echo \"::error::Unknown architecture: ${{ matrix.target }}\"\n            exit 1\n          fi\n      - name: 📦 Build .deb package\n        uses: jiro4989/build-deb-action@v4\n        with:\n          package: prqlc\n          package_root: .debpkg\n          maintainer: The PRQL Project\n          version: ${{ github.event_name == 'release' && github.ref_name || 0 }}\n          arch: ${{ env.package_arch }}\n          desc: >\n            prqlc is the CLI for the PRQL compiler. It compiles PRQL to SQL, and\n            offers various diagnostics.\n\n            PRQL is a modern language for transforming data — a simple,\n            powerful, pipelined SQL replacement.\n      - uses: actions/upload-artifact@v5\n        with:\n          name: deb-${{ matrix.target }}\n          path: ./*.deb\n      - name: Release\n        if: github.event_name == 'release'\n        uses: softprops/action-gh-release@v2\n        with:\n          files: prqlc_*.deb\n\n  build-rpm-package:\n    runs-on: ubuntu-24.04\n    strategy:\n      fail-fast: false\n      matrix:\n        target:\n          - x86_64-unknown-linux-musl\n          #- aarch64-unknown-linux-musl # https://github.com/jiro4989/build-rpm-action/issues/6\n    needs: build-prqlc\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/download-artifact@v6\n        with:\n          name: prqlc-${{ matrix.target }}-release\n      - name: Copy files into .rpm package\n        run: |\n          tar -xf prqlc-*.tar.gz\n          mkdir -p .rpmpkg/usr/bin\n          mv prqlc .rpmpkg/usr/bin/prqlc\n          chmod +x .rpmpkg/usr/bin/prqlc\n      - name: Set variables\n        run: |\n          if [ \"${{ matrix.target }}\" == \"aarch64-unknown-linux-musl\" ]; then\n            echo \"package_arch=aarch64\" >> \"$GITHUB_ENV\"\n          elif [ \"${{ matrix.target }}\" == \"x86_64-unknown-linux-musl\" ]; then\n            echo \"package_arch=x86_64\" >> \"$GITHUB_ENV\"\n          else\n            echo \"::error::Unknown architecture: ${{ matrix.target }}\"\n          fi\n      - name: 📦 Build .rpm package\n        uses: jiro4989/build-rpm-action@v2\n        with:\n          summary: CLI for PRQL, a modern language for transforming data\n          package: prqlc\n          package_root: .rpmpkg\n          maintainer: The PRQL Project\n          vendor: The PRQL Project\n          version: ${{ github.event_name == 'release' && github.ref_name || 0 }}\n          arch: ${{ env.package_arch }}\n          desc: >\n            prqlc is the CLI for the PRQL compiler. It compiles PRQL to SQL, and\n            offers various diagnostics.\n\n            PRQL is a modern language for transforming data — a simple,\n            powerful, pipelined SQL replacement.\n          license: Apache-2.0\n      - uses: actions/upload-artifact@v5\n        with:\n          name: artifact-rpm\n          path: |\n            ./*.rpm\n            !./*-debuginfo-*.rpm\n      - run: rm prqlc-debuginfo-*.rpm\n      - name: Release\n        if: github.event_name == 'release'\n        uses: softprops/action-gh-release@v2\n        with:\n          files: prqlc-*.rpm\n\n  build-and-publish-snap:\n    runs-on: ubuntu-24.04\n    if: ${{ github.event_name == 'release' }}\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - name: Move Snap to project root directory\n        run: cp -r prqlc/packages/snap/ .\n      - name: 📦 Build Snap\n        id: build\n        uses: snapcore/action-build@v1\n      - name: 🆙 Publish Snap\n        if: github.event_name == 'release'\n        uses: snapcore/action-publish@v1\n        env:\n          SNAPCRAFT_STORE_CREDENTIALS:\n            ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}\n        with:\n          snap: ${{ steps.build.outputs.snap }}\n          release: stable\n\n  build-python-wheels:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: true\n      matrix:\n        os: [ubuntu-24.04, windows-latest]\n        package:\n          - prqlc-python\n        target: [x86_64]\n        include:\n          # MacOS with universal builds\n          - os: macos-15\n            package: prqlc-python\n            target: universal2-apple-darwin\n          # Also produce more targets for ubuntu:\n          - os: ubuntu-24.04\n            package: prqlc-python\n            target: aarch64\n          - os: ubuntu-24.04\n            package: prqlc-python\n            target: source\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: ./.github/actions/build-python\n        with:\n          target: ${{ matrix.target }}\n          package: ${{ matrix.package }}\n          profile: release\n\n  publish-python:\n    runs-on: ubuntu-24.04\n    needs: [build-python-wheels]\n    if: github.event_name == 'release'\n    permissions:\n      id-token: write\n    strategy:\n      matrix:\n        package:\n          - prqlc-python\n    steps:\n      - uses: actions/download-artifact@v6\n        with:\n          # `*` covers target & OS\n          pattern: ${{ matrix.package }}-*-release\n          merge-multiple: true\n      - name: Publish to PyPI\n        uses: PyO3/maturin-action@v1\n        with:\n          command: upload\n          args: --skip-existing *\n\n  publish-js:\n    runs-on: ubuntu-24.04\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: wasm-pack\n\n      - name: Setup Node\n        uses: actions/setup-node@v6\n        with:\n          # Node 24+ includes npm 11.5.1+ required for OIDC trusted publishing\n          node-version: \"24.x\"\n          registry-url: \"https://registry.npmjs.org\"\n\n      - run: ./.github/workflows/scripts/set_version.sh\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: ${{ env.version }}-${{ hashFiles('./Cargo.lock') }}\n          # Share key with the `build-web` job\n          shared-key: web\n          save-if: false\n\n      # This is only required in order to have `cross-env` installed, since `npx\n      # cross-env` doesn't seem to work in CI (https://github.com/PRQL/prql/pull/3728)\n      - run: npm install\n        working-directory: prqlc/bindings/js/\n\n      - name: Publish to npm\n        run:\n          npm publish --provenance --access public ${{ (github.event_name !=\n          'release') && '--dry-run' || '' }}\n        working-directory: prqlc/bindings/js/\n\n  publish-to-cargo:\n    runs-on: ubuntu-24.04\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-release\n      # Currently, we can only check prqlc-parser which is not dependent other local crates with --dry-run.\n      # https://github.com/crate-ci/cargo-release/issues/691\n      # --no-verify is required to prevent build.\n      - run:\n          cargo release publish --no-confirm ${{ github.event_name == 'release'\n          && '--execute' || '--no-verify --package prqlc-parser'}}\n        env:\n          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n\n  # Requires another pass: https://github.com/PRQL/prql/issues/850\n  # publish-prql-java:\n  #   runs-on: ubuntu-24.04\n  #   steps:\n  #     - name: Checkout code\n  #       uses: actions/checkout@v5\n  #     - name: Install Java and Maven\n  #       uses: actions/setup-java@v3\n  #       with:\n  #         java-version: 8\n  #     - name: Release Maven package\n  #       uses: samuelmeuli/action-maven-publish@v1\n  #       with:\n  #         gpg_private_key: ${{ secrets.gpg_private_key }}\n  #         gpg_passphrase: ${{ secrets.gpg_passphrase }}\n  #         nexus_username: ${{ secrets.nexus_username }}\n  #         nexus_password: ${{ secrets.nexus_password }}\n  #         directory: prql-java/java/\n\n  push-web-branch:\n    runs-on: ubuntu-24.04\n    if: github.event_name == 'release'\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n        with:\n          token: ${{ secrets.PRQL_BOT_GITHUB_TOKEN }}\n      - run: git push origin HEAD:web --force\n\n  push-devcontainer:\n    if: github.event_name == 'release'\n    uses: ./.github/workflows/build-devcontainer.yaml\n    with:\n      push: true\n"
  },
  {
    "path": ".github/workflows/scripts/set_version.sh",
    "content": "#!/bin/bash\n\n# We set prefix-key to the version from Cargo.toml for Swatinem/rust-cache@v2\n# since the caches seem to accumulate cruft over time;\n# ref https://github.com/PRQL/prql/pull/2407\n\nversion=$(cargo metadata --format-version=1 --no-deps | jq --raw-output '.packages[] | select(.name == \"prqlc\") | .version')\necho \"version=${version}\" >>\"$GITHUB_ENV\"\n"
  },
  {
    "path": ".github/workflows/scripts/util_free_space.sh",
    "content": "#!/usr/bin/env bash\n\n# From https://github.com/apache/arrow/blob/4011058f4a56bdcf160f46373355ffa0e22bcd2c/ci/scripts/util_free_space.sh\n\n#\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n\nset -eux\n\nif [ \"${GITHUB_ACTIONS}\" = \"true\" ]; then\n    df -h\n    echo \"::group::/usr/local/*\"\n    du -hsc /usr/local/*\n    echo \"::endgroup::\"\n    # ~1GB\n    sudo rm -rf \\\n        /usr/local/aws-cli \\\n        /usr/local/aws-sam-cil \\\n        /usr/local/julia* || :\n    echo \"::group::/usr/local/bin/*\"\n    du -hsc /usr/local/bin/*\n    echo \"::endgroup::\"\n    # ~1GB (From 1.2GB to 214MB)\n    sudo rm -rf \\\n        /usr/local/bin/aliyun \\\n        /usr/local/bin/aws \\\n        /usr/local/bin/aws_completer \\\n        /usr/local/bin/azcopy \\\n        /usr/local/bin/bicep \\\n        /usr/local/bin/cmake-gui \\\n        /usr/local/bin/cpack \\\n        /usr/local/bin/helm \\\n        /usr/local/bin/hub \\\n        /usr/local/bin/kubectl \\\n        /usr/local/bin/minikube \\\n        /usr/local/bin/node \\\n        /usr/local/bin/packer \\\n        /usr/local/bin/pulumi* \\\n        /usr/local/bin/sam \\\n        /usr/local/bin/stack \\\n        /usr/local/bin/terraform || :\n    # 142M\n    sudo rm -rf /usr/local/bin/oc || : \\\n        echo \"::group::/usr/local/share/*\"\n    du -hsc /usr/local/share/*\n    echo \"::endgroup::\"\n    # 506MB\n    sudo rm -rf /usr/local/share/chromium || :\n    # 1.3GB\n    sudo rm -rf /usr/local/share/powershell || :\n    echo \"::group::/usr/local/lib/*\"\n    du -hsc /usr/local/lib/*\n    echo \"::endgroup::\"\n    # 15GB\n    sudo rm -rf /usr/local/lib/android || :\n    # 341MB\n    sudo rm -rf /usr/local/lib/heroku || :\n    # 1.2GB\n    sudo rm -rf /usr/local/lib/node_modules || :\n    echo \"::group::/opt/*\"\n    du -hsc /opt/*\n    echo \"::endgroup::\"\n    # 679MB\n    sudo rm -rf /opt/az || :\n    echo \"::group::/opt/microsoft/*\"\n    du -hsc /opt/microsoft/*\n    echo \"::endgroup::\"\n    # 197MB\n    sudo rm -rf /opt/microsoft/powershell || :\n    echo \"::group::/opt/hostedtoolcache/*\"\n    du -hsc /opt/hostedtoolcache/*\n    echo \"::endgroup::\"\n    # 5.3GB\n    sudo rm -rf /opt/hostedtoolcache/CodeQL || :\n    # 1.4GB\n    sudo rm -rf /opt/hostedtoolcache/go || :\n    # 489MB\n    sudo rm -rf /opt/hostedtoolcache/PyPy || :\n    # 376MB\n    sudo rm -rf /opt/hostedtoolcache/node || :\n    # Remove Web browser packages\n    sudo apt purge -y \\\n        firefox \\\n        google-chrome-stable \\\n        microsoft-edge-stable\n    df -h\nfi\n"
  },
  {
    "path": ".github/workflows/test-dotnet.yaml",
    "content": "name: test-dotnet\n\non:\n  workflow_call:\n  workflow_dispatch:\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  test:\n    runs-on: ubuntu-24.04\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - name: 🏗 Build prqlc-c\n        run: cargo build --package prqlc-c\n      - name: 🔧 Setup dotnet\n        uses: actions/setup-dotnet@v5\n        with:\n          dotnet-version: 7\n      - name: 🧪 Build and test\n        working-directory: prqlc/bindings\n        run: |\n          dotnet build dotnet\n          cp ../../target/debug/libprqlc_c.* dotnet/PrqlCompiler/bin/Debug/net*/\n          cp ../../target/debug/libprqlc_c.* dotnet/PrqlCompiler.Tests/bin/Debug/net*/\n          dotnet test dotnet\n"
  },
  {
    "path": ".github/workflows/test-elixir.yaml",
    "content": "name: test-elixir\n\non:\n  workflow_call:\n    inputs:\n      oss:\n        type: string\n        default: '[\"ubuntu-24.04\"]'\n  workflow_dispatch:\n    inputs:\n      oss:\n        type: string\n        default: '[\"ubuntu-24.04\"]'\n\ndefaults:\n  run:\n    working-directory: prqlc/bindings/elixir\n\nenv:\n  MIX_ENV: test\n  # We need consistent env vars across all workflows for the cache to work\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  test:\n    strategy:\n      matrix:\n        os: ${{ fromJSON(inputs.oss) }}\n        otp: [\"25.1.2\"]\n        elixir: [\"1.15.7\"]\n    runs-on: ${{matrix.os}}\n    steps:\n      # Step: Check out the code.\n      - name: Checkout code\n        uses: actions/checkout@v5\n\n      # Step: Setup Elixir + Erlang image as the base.\n      - name: Set up Elixir on Windows or Linux\n        if: runner.os != 'macOS'\n        uses: erlef/setup-beam@v1.20.4\n        with:\n          otp-version: ${{matrix.otp}}\n          elixir-version: ${{matrix.elixir}}\n\n      - name: Install Erlang/Elixir on Mac\n        if: runner.os == 'macOS'\n        run: |\n          brew install elixir\n          mix local.hex --force\n\n      # Step: Define how to cache deps. Restores existing cache if present.\n      - name: Cache deps\n        id: cache-deps\n        uses: actions/cache@v4\n        env:\n          cache-name: cache-elixir-deps\n        with:\n          path: elixir/deps\n          key:\n            ${{ runner.os }}-mix-${{ env.cache-name }}-${{\n            hashFiles('**/mix.lock') }}\n          restore-keys: |\n            ${{ runner.os }}-mix-${{ env.cache-name }}-\n\n      # Step: Define how to cache the `_build` directory. After the first run,\n      # this speeds up tests runs a lot. This includes not re-compiling our\n      # project's downloaded deps every run.\n      - name: Cache compiled build\n        id: cache-build\n        uses: actions/cache@v4\n        env:\n          cache-name: cache-compiled-build\n        with:\n          path: elixir/_build\n          key:\n            ${{ runner.os }}-mix-${{ env.cache-name }}-${{\n            hashFiles('**/mix.lock') }}\n          restore-keys: |\n            ${{ runner.os }}-mix-${{ env.cache-name }}-\n            ${{ runner.os }}-mix-\n\n      # Step: Download project dependencies. If unchanged, uses\n      # the cached version.\n      - name: Install dependencies\n        run: mix deps.get\n\n      # Step: Compile the project treating any warnings as errors.\n      # Customize this step if a different behavior is desired.\n      - name: Compiles without warnings\n        run: mix compile --warnings-as-errors\n\n      # Step: Check that the checked in code has already been formatted.\n      # This step fails if something was found unformatted.\n      # Customize this step as desired.\n      - name: Check Formatting\n        run: mix format --check-formatted\n\n      # Step: Execute the tests.\n      - name: Run tests\n        run: mix test\n"
  },
  {
    "path": ".github/workflows/test-java.yaml",
    "content": "name: test-java\n\non:\n  workflow_call:\n    inputs:\n      oss:\n        type: string\n        default: '[\"ubuntu-24.04\"]'\n  workflow_dispatch:\n    inputs:\n      oss:\n        type: string\n        default: '[\"ubuntu-24.04\"]'\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: ${{ fromJSON(inputs.oss) }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n      - run: ./.github/workflows/scripts/set_version.sh\n      - name: Compute Cargo.lock hash\n        shell: bash\n        run: |\n          if command -v sha256sum &> /dev/null; then\n            echo \"cargo_lock_hash=$(sha256sum Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n          else\n            echo \"cargo_lock_hash=$(shasum -a 256 Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n          fi\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: ${{ env.version }}-${{ env.cargo_lock_hash }}\n          save-if: ${{ github.ref == 'refs/heads/main' }}\n          shared-key: lib\n      - name: Maven test\n        working-directory: prqlc/bindings/java/java/\n        run: ./mvnw test\n"
  },
  {
    "path": ".github/workflows/test-js.yaml",
    "content": "name: test-js\n\non:\n  workflow_call:\n    inputs:\n      oss:\n        type: string\n        default: '[\"ubuntu-24.04\"]'\n  workflow_dispatch:\n    inputs:\n      oss:\n        type: string\n        default: '[\"ubuntu-24.04\"]'\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    continue-on-error: ${{ matrix.os == 'windows-latest' }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: ${{ fromJSON(inputs.oss) }}\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n\n      - name: Setup Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"21.x\"\n\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: wasm-pack\n\n      - run: ./.github/workflows/scripts/set_version.sh\n\n      - name: Compute Cargo.lock hash\n        shell: bash\n        run: |\n          if command -v sha256sum &> /dev/null; then\n            echo \"cargo_lock_hash=$(sha256sum Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n          else\n            echo \"cargo_lock_hash=$(shasum -a 256 Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n          fi\n\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        id: cache\n        with:\n          prefix-key: ${{ env.version }}-${{ env.cargo_lock_hash }}\n          # `web` is the closest rust cache key. It's useful on ubuntu (last\n          # checked, roughly halved the time, to 4 min), but not on other OSs\n          # given we don't have those caches. We can't use a separate one given\n          # we're out of cache space.\n          shared-key: web\n          save-if: false\n\n      - run: npm cit\n        working-directory: prqlc/bindings/js\n"
  },
  {
    "path": ".github/workflows/test-php.yaml",
    "content": "name: test-php\n\non:\n  workflow_call:\n  workflow_dispatch:\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-24.04]\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: go-task/setup-task@v1\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n      - run: ./.github/workflows/scripts/set_version.sh\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: ${{ env.version }}-${{ hashFiles('./Cargo.lock') }}\n          save-if: ${{ github.ref == 'refs/heads/main' }}\n          shared-key: lib\n      - run: task build-php\n      - run: task test-php\n"
  },
  {
    "path": ".github/workflows/test-prqlc-c.yaml",
    "content": "name: test-prqlc-c\n\non:\n  workflow_call:\n  workflow_dispatch:\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  test-c:\n    runs-on: ubuntu-24.04\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - run: ./.github/workflows/scripts/set_version.sh\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: ${{ env.version }}-${{ hashFiles('./Cargo.lock') }}\n          save-if: ${{ github.ref == 'refs/heads/main' }}\n          shared-key: lib\n      - name: Build\n        uses: clechasseur/rs-cargo@v4\n        with:\n          command: build\n          # Currently requires a release build; would be useful to allow a debug build.\n          args: --release --package prqlc-c\n      - name: Run example minimal-c\n        working-directory: prqlc/bindings/prqlc-c/examples/minimal-c\n        run: make run\n      - name: Run example minimal-cpp\n        working-directory: prqlc/bindings/prqlc-c/examples/minimal-cpp\n        run: make run\n\n      - uses: go-task/setup-task@v1\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n      - name: 🔧 Setup Zig\n        uses: mlugg/setup-zig@v2\n      - name: Run example minimal-zig\n        run: task zig\n"
  },
  {
    "path": ".github/workflows/test-python.yaml",
    "content": "name: test-python\n\non:\n  workflow_call:\n    inputs:\n      oss:\n        type: string\n        default: '[\"ubuntu-24.04\"]'\n  workflow_dispatch:\n    inputs:\n      oss:\n        type: string\n        default: '[\"ubuntu-24.04\"]'\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: ${{ fromJSON(inputs.oss) }}\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - name: Build wheel\n        uses: ./.github/actions/build-python\n        with:\n          target: ${{ matrix.os == 'ubuntu-24.04' && 'x86_64' || '' }}\n          package: prqlc-python\n          profile: dev\n      - uses: actions/download-artifact@v6\n        with:\n          # `*` covers all targets (we could make this explicit...)\n          pattern: prqlc-python-${{ runner.os }}-*-dev\n          path: target/python\n      - uses: actions/setup-python@v6\n        with:\n          python-version: \"3.10\"\n      - uses: actions/setup-python@v6\n        with:\n          python-version: \"3.12\"\n      - name: Install uv\n        uses: astral-sh/setup-uv@v7\n      - name: Install nox\n        run: uv tool install nox\n        shell: bash\n      - name: Compute pyproject.toml hash\n        shell: bash\n        run: |\n          if command -v sha256sum &> /dev/null; then\n            echo \"pyproject_hash=$(sha256sum prqlc/bindings/prqlc-python/pyproject.toml | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n          else\n            echo \"pyproject_hash=$(shasum -a 256 prqlc/bindings/prqlc-python/pyproject.toml | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n          fi\n      - name: Cache uv and Nox\n        uses: actions/cache@v4\n        with:\n          path: |\n            .nox\n            ~/.cache/uv\n          key: nox-uv-${{ env.pyproject_hash }}\n      - name: Run tests and typing\n        shell: bash\n        run: |\n          export UV_SYSTEM_PYTHON=1\n          nox -s tests typing -f prqlc/bindings/prqlc-python/noxfile.py\n"
  },
  {
    "path": ".github/workflows/test-rust.yaml",
    "content": "name: test-rust\n\non:\n  # Currently we only run this as `workflow_call`, since `tests.yaml` always calls it.\n  workflow_call:\n    inputs:\n      os:\n        type: string\n        required: true\n      target:\n        type: string\n        required: true\n      features:\n        type: string\n        required: true\n      nightly:\n        description: \"Whether to run extra tests (this is not nightly rust)\"\n        type: boolean\n        default: false\n\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  # This used to reduce the size of the cargo cache by ~25%. It's not as\n  # effective as it once was, as explained in\n  # https://github.com/PRQL/prql/pull/2797\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  test-rust:\n    runs-on: ${{ inputs.os }}\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n        with:\n          fetch-tags: true\n      - name: Run docker compose\n        # This can go early because the DBs take a few seconds to start up.\n        if: ${{ contains(inputs.features, 'test-dbs-external') }}\n        run: docker compose up -d\n        working-directory: ./prqlc/prqlc/tests/integration/dbs\n      - if: ${{ contains(inputs.target, 'musl') }}\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y musl-tools\n      - run: rustup target add ${{ inputs.target }}\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: wasm-bindgen-cli\n        if: inputs.target == 'wasm32-unknown-unknown'\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-insta\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-nextest\n      - run: ./.github/workflows/scripts/set_version.sh\n        shell: bash\n      - name: Compute Cargo.lock hash\n        shell: bash\n        run: |\n          if command -v sha256sum &> /dev/null; then\n            echo \"cargo_lock_hash=$(sha256sum Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n          else\n            echo \"cargo_lock_hash=$(shasum -a 256 Cargo.lock | cut -d' ' -f1)\" >> \"$GITHUB_ENV\"\n          fi\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        id: cache\n        with:\n          # We add the hash of the Cargo.lock file to the key, to prevent the\n          # gradual accumulation of disk space that comes from using previous\n          # caches. The rust cache is designed to remove old packages, but isn't\n          # that successful at it, and our current cache size at ~1.3GB is about\n          # as much as GHA can handle, such that if we hold old packages, it\n          # balloons to 2GB and fails. Some discussion at:\n          # https://github.com/Swatinem/rust-cache/issues/177\n          prefix-key: ${{ env.version }}-${{ env.cargo_lock_hash }}\n          shared-key: rust-${{ inputs.target }}\n          # Don't save if empty features, because we run a test with empty\n          # features on linux, and don't want to save that instead of the one\n          # with all features. I tried including features in the key, but it\n          # contains a comma, which isn't allowed; a whole extra task to remove\n          # the comma seemed over the top.\n          save-if:\n            ${{ github.ref == 'refs/heads/main' && inputs.features != '' }}\n      - uses: actions/setup-python@v6\n        # python isn't natively installed on macos-15, so we need to install it\n        if: ${{ inputs.os == 'macos-15' }}\n        with:\n          python-version: \"3.11\"\n      - name: Free up disk space\n        # https://github.com/actions/runner-images/issues/2840\n        run: ./.github/workflows/scripts/util_free_space.sh\n        # This takes ~3 minutes, so we'd really prefer not to run it on every\n        # PR. Trying to run it only when there is no cache hit. We may need to\n        # remove that condition and run it whenever we pull the docker images,\n        # or explore breaking up the tests more (though that's not easy to get\n        # savings on either). Ideally GH will allow for more disk space in the\n        # future... (deleting things doesn't actually remove the amount of stuff\n        # that GH needs to store, so it's purely performative!)\n        if:\n          ${{ contains(inputs.features, 'test-dbs-external') &&\n          steps.cache.outputs.cache-hit == 'false' }}\n      # We split up the test compilation as recommended in\n      # https://matklad.github.io/2021/09/04/fast-rust-builds.html\n      - name: 🏭 Compile\n        uses: clechasseur/rs-cargo@v4\n        with:\n          command: test\n          args: >\n            --no-run --locked --target=${{ inputs.target }}\n            --no-default-features --features=${{ inputs.features }}\n      - name: Wait for database\n        uses: ifaxity/wait-on-action@v1.2.1\n        with:\n          resource: \"tcp:1433 tcp:3306 tcp:5432 tcp:9004\"\n          timeout: 60000\n        if: ${{ contains(inputs.features, 'test-dbs-external') }}\n      - name: 📋 Test\n        uses: clechasseur/rs-cargo@v4\n        with:\n          command: insta\n          # Here, we also add:\n          # - Unreferenced snapshots - `--unreferenced=auto` when testing on\n          #   linux & with `test-dbs` feature.\n          # - Test runner - `--test-runner=nextest` when not targeting wasm32.\n          # - Skip doc tests on Windows due to LNK1318 PDB errors\n          args: >\n            test --dnd --target=${{ inputs.target }} --no-default-features\n            --features=${{ inputs.features }} ${{ contains(inputs.features,\n            'test-dbs') && inputs.target == 'x86_64-unknown-linux-gnu' &&\n            '--unreferenced=auto' || '' }} ${{ inputs.target !=\n            'wasm32-unknown-unknown' && '--test-runner=nextest' || '' }} ${{\n            inputs.os == 'windows-latest' && '--lib --bins --tests --examples'\n            || '' }}\n      - name: 📋 Doctest\n        # Skip doctests on Windows (LNK1318 PDB errors) and wasm32\n        if:\n          inputs.os != 'windows-latest' && inputs.target !=\n          'wasm32-unknown-unknown'\n        uses: clechasseur/rs-cargo@v4\n        with:\n          command: test\n          args: >\n            --doc --target=${{ inputs.target }} --no-default-features\n            --features=${{ inputs.features }}\n      - name: 📎 Clippy\n        uses: clechasseur/rs-cargo@v4\n        with:\n          command: clippy\n          # Note that `--all-targets` doesn't refer to targets like\n          # `wasm32-unknown-unknown`; it refers to lib / bin / tests etc.\n          #\n          args: >\n            --all-targets --target=${{ inputs.target }} --no-default-features\n            --features=${{ inputs.features }} -- -D warnings\n      - name: ⌨️ Fmt\n        uses: clechasseur/rs-cargo@v4\n        with:\n          command: fmt\n          args: --all --check\n      - name: 🗒️ Doc\n        # Only running on nightly, because of\n        # https://github.com/duckdb/duckdb-rs/issues/179#issuecomment-1710986020.\n        if:\n          inputs.nightly == 'true' && inputs.target != 'wasm32-unknown-unknown'\n        uses: clechasseur/rs-cargo@v4\n        with:\n          command: doc\n          # Only run with deps on nightly, since it's much slower, and so far™\n          # we haven't seen any errors.\n          args:\n            --target=${{ inputs.target }} --no-default-features --features=${{\n            inputs.features }} ${{ inputs.nightly != 'true' && '--no-deps' || ''\n            }}\n      - name: Build extra targets for cache\n        # When building the cache, we also run with `--all-targets` so that\n        # prqlc builds can use the same cache.\n        if:\n          ${{ github.ref == 'refs/heads/main' && steps.cache.outputs.cache-hit\n          == 'false' }}\n        uses: clechasseur/rs-cargo@v4\n        with:\n          command: build\n          args:\n            --all-targets --target=${{ inputs.target }} --no-default-features\n            --features=${{ inputs.features }}\n"
  },
  {
    "path": ".github/workflows/tests.yaml",
    "content": "# This file has transitioning to run almost everything, with rules defined in\n# this file rather than across lots of workflow files.\nname: tests\n\non:\n  pull_request:\n    # Add `labeled`, so we can trigger a new run by adding a `pr-nightly`\n    # label, which we then use to trigger a `nightly` run.\n    types: [opened, reopened, synchronize, labeled]\n    branches:\n      - \"*\"\n  push:\n    branches:\n      - main\n  schedule:\n    # Pick a random time, something that others won't pick, to be good citizens\n    # and reduce GH's demand variance.\n    - cron: \"49 10 * * *\"\n  workflow_dispatch:\n  workflow_call:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}\n  cancel-in-progress: true\n\n# We need consistent env vars across all workflows for the cache to work\nenv:\n  CARGO_TERM_COLOR: always\n  CLICOLOR_FORCE: 1\n  RUSTFLAGS: \"-C debuginfo=0\"\n  RUSTDOCFLAGS: \"-Dwarnings\"\n\njobs:\n  # This assesses whether we need to run jobs. Some of them are defined only by\n  # the changes in PR, others also define a set of other criteria, such as\n  # whether a label has been added, or we're on `main` branch.\n  rules:\n    runs-on: ubuntu-24.04\n    permissions:\n      pull-requests: read\n    outputs:\n      book: ${{ steps.changes.outputs.book }}\n      dotnet: ${{ steps.changes.outputs.dotnet }}\n      devcontainer-push: ${{ steps.devcontainer-push.outputs.run }}\n      devcontainer-build: ${{ steps.devcontainer-build.outputs.run }}\n      elixir: ${{ steps.changes.outputs.elixir }}\n      grammars: ${{ steps.changes.outputs.grammars }}\n      java: ${{ steps.changes.outputs.java }}\n      js: ${{ steps.changes.outputs.js }}\n      prqlc-c: ${{ steps.changes.outputs.prqlc-c }}\n      # Run tests such as rust tests for all-OSs, and bindings tests on ubuntu.\n      # Somewhat a tradeoff between coverage and ensuring our CI queues stay\n      # short.\n      main: ${{ steps.main.outputs.run }}\n      # Run all tests\n      nightly: ${{ steps.nightly.outputs.run }}\n      # For tasks which are very expensive or can only run on\n      # the main repo, such as pushing devcontainer or creating issues\n      nightly-upstream: ${{ steps.nightly-upstream.outputs.run }}\n      php: ${{ steps.changes.outputs.php }}\n      python: ${{ steps.changes.outputs.python }}\n      rust: ${{ steps.changes.outputs.rust }}\n      taskfile: ${{ steps.changes.outputs.taskfile }}\n      web: ${{ steps.changes.outputs.web }}\n\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n        with:\n          fetch-tags: true\n      - uses: dorny/paths-filter@v3\n        id: changes\n        with:\n          filters: |\n            book:\n              - .github/workflows/check-links-book.yaml\n              - web/book/**\n            dotnet:\n              - prqlc/bindings/prql-dotnet/**\n              - prqlc/bindings/prqlc-c/**\n              - .github/workflows/test-dotnet.yaml\n            devcontainer-push:\n              - .devcontainer/**/*Dockerfile\n              - .github/workflows/build-devcontainer.yaml\n              - Taskfile.yaml\n            devcontainer-build:\n              - .devcontainer/**/*Dockerfile\n              - .github/workflows/build-devcontainer.yaml\n              - Taskfile.yaml\n            grammars:\n              - grammars/**\n            elixir:\n              - prqlc/bindings/elixir/**\n              - prqlc/bindings/prqlc-c/**\n              - .github/workflows/test-elixir.yaml\n            java:\n              - prqlc/bindings/java/**\n              - prqlc/bindings/prqlc-c/**\n              - .github/workflows/test-java.yaml\n            js:\n              - prqlc/bindings/js/**\n              - .github/workflows/test-js.yaml\n            prqlc-c:\n              - prqlc/bindings/prqlc-c/**\n              - .github/workflows/test-prqlc-c.yaml\n            main:\n              - \"**/Cargo.*\"\n              - .github/**\n              - .config/**\n            nightly:\n              - .github/workflows/nightly.yaml\n              - .github/workflows/release.yaml\n              - Cargo.toml\n              - Cargo.lock\n              - rust-toolchain.toml\n              - .cargo/**\n            php:\n              - prqlc/bindings/php/**\n              - prqlc/bindings/prqlc-c/**\n              - .github/workflows/test-php.yaml\n            python:\n              - prqlc/bindings/prqlc-python/**\n              - .github/workflows/test-python.yaml\n            rust:\n              - \"**/*.rs\"\n              - prqlc/**\n              - web/book/**\n              - .github/workflows/test-rust.yaml\n            taskfile:\n              # Run taskfile test on any Taskfile change, since the tasks pull in tasks from\n              # other taskfiles. (But we don't run the container rebuilds, since those are\n              # much heavier)\n              - \"**/Taskfile.yaml\"\n            web:\n              - \"web/**\"\n              - \".github/workflows/build-web.yaml\"\n              - \"**.md\"\n\n      # We put a few of the more complex rules as steps here, rather than having\n      # them inline. There's no strict delineation between logic here vs. inline.\n\n      - id: nightly\n        # TODO: actionlint annoyingly blocks this — try and find a way of getting\n        # it back without too much trouble...\n        # contains(github.event.pull_request.title, '!') ||\n        run:\n          echo \"run=${{ steps.changes.outputs.nightly == 'true' ||\n          contains(github.event.pull_request.labels.*.name, 'pr-nightly') ||\n          github.event_name == 'schedule' }}\" >>\"$GITHUB_OUTPUT\"\n\n      - id: nightly-upstream\n        run:\n          echo \"run=${{ github.event_name == 'schedule' &&\n          github.repository_owner == 'prql' }}\" >>\"$GITHUB_OUTPUT\"\n\n      - id: main\n        run:\n          echo \"run=${{ steps.changes.outputs.main == 'true' || github.ref ==\n          'refs/heads/main' || steps.nightly.outputs.run == 'true' }}\" >>\n          \"$GITHUB_OUTPUT\"\n\n      - id: devcontainer-push\n        # We push the devcontainer if the files have changed, and we've merged\n        # to main.\n        run:\n          echo \"run=${{ steps.changes.outputs.devcontainer-push == 'true' &&\n          github.ref == 'refs/heads/main' && github.event_name == 'push' }}\" >>\n          \"$GITHUB_OUTPUT\"\n\n      - id: devcontainer-build\n        run:\n          echo \"run=${{ steps.devcontainer-push.outputs.run == 'true' ||\n          steps.changes.outputs.devcontainer-build == 'true' ||\n          steps.changes.outputs.nightly-upstream == 'true' }}\" >>\n          \"$GITHUB_OUTPUT\"\n\n  test-rust:\n    needs: rules\n    if: needs.rules.outputs.rust == 'true' || needs.rules.outputs.main == 'true'\n    uses: ./.github/workflows/test-rust.yaml\n    strategy:\n      matrix:\n        include:\n          - target: x86_64-unknown-linux-gnu\n            os: ubuntu-24.04\n            features: default,test-dbs-external\n          # Only run wasm on ubuntu, given it's the same rust target. (There is\n          # a possibility of having a failure on just one platform, but it's\n          # quite unlikely. If we do observe this, we can add those tests them\n          # to nightly.\n          - target: wasm32-unknown-unknown\n            os: ubuntu-24.04\n            features: default\n    with:\n      os: ubuntu-24.04\n      target: ${{ matrix.target }}\n      features: ${{ matrix.features }}\n      nightly: ${{ needs.rules.outputs.nightly == 'true' }}\n\n  test-python:\n    needs: rules\n    if:\n      needs.rules.outputs.python == 'true' || needs.rules.outputs.main == 'true'\n    uses: ./.github/workflows/test-python.yaml\n    with:\n      # Only run on ubuntu unless there's a lang-specific change or we're\n      # running nightly.\n      #\n      # An alternative to these somewhat horrible expressions would be\n      # `test-python` & `test-python-more` workflows; though it would use up our\n      # 20 workflow limit.\n      oss:\n        ${{ (needs.rules.outputs.python == 'true' || needs.rules.outputs.nightly\n        == 'true') && '[\"ubuntu-24.04\", \"macos-15\", \"windows-latest\"]' ||\n        '[\"ubuntu-24.04\"]' }}\n\n  test-js:\n    needs: rules\n    if: needs.rules.outputs.js == 'true' || needs.rules.outputs.main == 'true'\n    uses: ./.github/workflows/test-js.yaml\n    with:\n      # Only run on ubuntu unless there's a lang-specific change or we're running nightly.\n      oss:\n        ${{ (needs.rules.outputs.js == 'true' || needs.rules.outputs.nightly ==\n        'true') && '[\"ubuntu-24.04\", \"macos-15\", \"windows-latest\"]' ||\n        '[\"ubuntu-24.04\"]' }}\n\n  test-dotnet:\n    needs: rules\n    if:\n      needs.rules.outputs.dotnet == 'true' || needs.rules.outputs.main == 'true'\n    uses: ./.github/workflows/test-dotnet.yaml\n\n  test-php:\n    needs: rules\n    if: needs.rules.outputs.php == 'true' || needs.rules.outputs.main == 'true'\n    uses: ./.github/workflows/test-php.yaml\n\n  test-java:\n    needs: rules\n    if: needs.rules.outputs.java == 'true' || needs.rules.outputs.main == 'true'\n    uses: ./.github/workflows/test-java.yaml\n    with:\n      # Currently we never run windows\n      oss:\n        ${{ (needs.rules.outputs.java == 'true' || needs.rules.outputs.nightly\n        == 'true') && '[\"ubuntu-24.04\", \"macos-15\"]' || '[\"ubuntu-24.04\"]' }}\n\n  test-elixir:\n    needs: rules\n    if:\n      needs.rules.outputs.elixir == 'true' || needs.rules.outputs.main == 'true'\n    uses: ./.github/workflows/test-elixir.yaml\n    with:\n      # Currently we never run Mac, see prql-elixir docs for details\n      oss:\n        ${{ (needs.rules.outputs.elixir == 'true' || needs.rules.outputs.nightly\n        == 'true') && '[\"ubuntu-24.04\", \"windows-2022\"]' || '[\"ubuntu-24.04\"]'\n        }}\n\n  test-prqlc-c:\n    needs: rules\n    if:\n      needs.rules.outputs.prqlc-c == 'true' || needs.rules.outputs.main ==\n      'true'\n    uses: ./.github/workflows/test-prqlc-c.yaml\n\n  # Disabled due to https://github.com/PRQL/prql/pull/4876\n  # To re-enable, uncomment this entire job block\n  # test-taskfile:\n  #   needs: rules\n  #   if: |\n  #     needs.rules.outputs.taskfile == 'true' ||\n  #     needs.rules.outputs.nightly-upstream == 'true'\n  #   runs-on: macos-15\n  #   steps:\n  #     - name: 📂 Checkout code\n  #       uses: actions/checkout@v5\n  #     - run: ./.github/workflows/scripts/set_version.sh\n  #     - uses: actions/setup-python@v6\n  #       with:\n  #         python-version: \"3.11\"\n  #     - name: 💰 Cache\n  #       uses: Swatinem/rust-cache@v2\n  #       with:\n  #         prefix-key: ${{ env.version }}-${{ hashFiles('./Cargo.lock') }}\n  #         # The mac rust cache key. It's not _that_ useful since this will build\n  #         # much more, but it's better than nothing. This task can't have our own\n  #         # cache, since we're out of cache space and this workflow takes 1.5GB.\n  #         shared-key: rust-aarch64-apple-darwin\n  #         save-if: false\n  #     - name: Install Task\n  #       uses: go-task/setup-task@v1\n  #       with:\n  #         repo-token: ${{ secrets.GITHUB_TOKEN }}\n  #     # Required because of https://github.com/cargo-bins/cargo-binstall/issues/1254\n  #     - run: brew install bash\n  #     - run: task install-brew-dependencies\n  #     - run: task setup-dev\n  #     # This also encompasses `build-all`\n  #     - run: task test-all\n  #     - run: task prqlc:test\n  #     - run: task lint\n\n  test-rust-main:\n    needs: rules\n    if: needs.rules.outputs.main == 'true'\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - os: macos-15\n            target: aarch64-apple-darwin\n            features: default,test-dbs\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n            # We'd like to reenable integration tests on Windows, ref https://github.com/wangfenjin/duckdb-rs/issues/179.\n            features: default\n          - os: ubuntu-24.04\n            target: x86_64-unknown-linux-gnu\n            # One test with no features\n            features: \"\"\n\n          # TODO: potentially enable these\n          # - os: ubuntu-24.04\n          #   target: aarch64-unknown-linux-musl\n\n    uses: ./.github/workflows/test-rust.yaml\n    with:\n      os: ${{ matrix.os }}\n      target: ${{ matrix.target }}\n      features: ${{ matrix.features }}\n\n  build-web:\n    needs: rules\n    if: needs.rules.outputs.web == 'true' || needs.rules.outputs.main == 'true'\n    uses: ./.github/workflows/build-web.yaml\n\n  lint-megalinter:\n    uses: ./.github/workflows/lint-megalinter.yaml\n\n  publish-web:\n    uses: ./.github/workflows/publish-web.yaml\n    if: contains(github.event.pull_request.labels.*.name, 'pr-publish-web')\n\n  nightly:\n    needs: rules\n    uses: ./.github/workflows/nightly.yaml\n    if: needs.rules.outputs.nightly == 'true'\n    secrets: inherit\n\n  check-links-markdown:\n    needs: rules\n    # Using lychee via pre-commit for consistency with local development.\n    # Lychee has better retry support for transient network errors than markdown-link-check.\n    runs-on: ubuntu-24.04\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: lychee\n\n      - uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n\n      - name: Install pre-commit\n        run: pip install pre-commit\n\n      - name: Cache lychee results\n        uses: actions/cache@v4\n        with:\n          path: .lycheecache\n          key: lychee-${{ github.sha }}\n          restore-keys: lychee-\n\n      # For PRs: get list of modified markdown files\n      - name: Get modified files\n        if: needs.rules.outputs.nightly != 'true'\n        id: changed-files\n        uses: tj-actions/changed-files@v47\n        with:\n          files: |\n            **/*.md\n            **/*.rst\n\n      - name: Link Checker (All files - Nightly)\n        if: needs.rules.outputs.nightly == 'true'\n        run: pre-commit run --hook-stage manual lychee-all --all-files\n        continue-on-error: true\n\n      - name: Link Checker (Modified files only - PRs)\n        if:\n          needs.rules.outputs.nightly != 'true' &&\n          steps.changed-files.outputs.any_modified == 'true'\n        run:\n          pre-commit run lychee --files ${{\n          steps.changed-files.outputs.all_changed_files }}\n        continue-on-error: true\n\n  check-links-book:\n    # We also have a check-links-markdown job, however it will not spot mdbook\n    # mistakes such as forgetting to list an .md file in SUMMARY.md.\n    # Running a link checker on the generated HTML is more reliable.\n    needs: rules\n    if:\n      needs.rules.outputs.book == 'true' || needs.rules.outputs.nightly ==\n      'true'\n\n    runs-on: ubuntu-24.04\n\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: mdbook\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: mdbook-footnote\n      # the link checker\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: hyperlink\n      - run: ./.github/workflows/scripts/set_version.sh\n      - name: Cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: ${{ env.version }}-${{ hashFiles('./Cargo.lock') }}\n          shared-key: web\n          # Created by `build-web`\n          save-if: false\n      # Only build the book — rather than `build-web` which also builds the playground\n      - name: Build the mdbook\n        run: mdbook build web/book/\n      - name: Check links\n        run: hyperlink web/book/book/\n\n  measure-code-cov:\n    runs-on: ubuntu-24.04\n    needs: rules\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - run: ./.github/workflows/scripts/set_version.sh\n      - uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-llvm-cov\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: ${{ env.version }}-${{ hashFiles('./Cargo.lock') }}\n          save-if: ${{ github.ref == 'refs/heads/main' }}\n      # Ensure nothing remains from caching\n      - run: cargo llvm-cov clean --workspace\n      - run:\n          # We considered moving to using `codecov.json` with\n          # `--codecov --output-path=codecov.json` since that has branch & region\n          # coverage. But the coverage is lower, in a way that doesn't represent\n          # what is useful coverage\n          cargo llvm-cov --cobertura --output-path=cobertura.xml\n          --no-default-features --features=default,test-dbs\n      - name: Upload code coverage results\n        uses: actions/upload-artifact@v5\n        with:\n          name: code-coverage-report\n          path: cobertura.xml\n      - name: Upload to codecov.io\n        if:\n          # This action raises an error on forks. It allows running on PRs to\n          # the main repo, which is important. Rarely do we need this uploading\n          # from forks so while we can reenable running from forks if it works,\n          # it's not that important.\n          #\n          # As of 2024-06, codecov was still working through how they handle\n          # forks / tokens on PRs given rate limits, expect some failures for a\n          # bit.\n          #\n          # As of 2024-06, we're also seeing that uploading on schedule can\n          # measure very slightly different coverage, which can then cause PRs\n          # based off that base to show reduced coverage, and show a failure. So\n          # we disable it on schedule. Not sure that's a perfect solution — is\n          # it giving different coverage _because_ it's on schedule, or is it\n          # random such that limiting to running on main will sometimes show\n          # reduced coverage? Because we're making comparisons, reproducible\n          # accuracy is important.\n          ${{ github.repository_owner == 'prql' && github.event_name !=\n          'schedule' }}\n        uses: codecov/codecov-action@v5\n        with:\n          files: cobertura.xml\n          fail_ci_if_error: true\n          # As discussed in\n          # https://community.codecov.com/t/upload-issues-unable-to-locate-build-via-github-actions-api/3954,\n          # without this the upload has a fairly high failure rate. The only\n          # thing the token allows is uploading coverage, so there are\n          # apparently no security risks.\n          #\n          # Edit: actually no luck, waiting on\n          # https://github.com/codecov/codecov-action/issues/1469\n          token: cab4ace5-4f10-4027-8b5c-d79722234571\n\n  test-grammars:\n    # Currently tests lezer grammars. We could split that out into a separate\n    # job if we want when we add more.\n    runs-on: ubuntu-24.04\n    needs: rules\n    if:\n      needs.rules.outputs.grammars == 'true' || needs.rules.outputs.main ==\n      'true'\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - name: 🧅 Setup Bun\n        uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n      - name: Install dependencies\n        working-directory: grammars/prql-lezer/\n        run: bun install\n      - name: Build grammar\n        working-directory: grammars/prql-lezer/\n        run: bun run build\n      - name: Test grammar\n        working-directory: grammars/prql-lezer/\n        run: bun run test\n\n  build-devcontainer:\n    needs: rules\n    if: needs.rules.outputs.devcontainer-build == 'true'\n    uses: ./.github/workflows/build-devcontainer.yaml\n    # One problem with this setup is that if another commit is merged to main,\n    # this workflow will cancel existing jobs, and so this won't get pushed. We\n    # have another workflow which runs on each release, so the image should get\n    # pushed eventually. The alternative is to have a separate workflow, but\n    # then we can't use the nice logic of when to run the workflow that we've\n    # built up here.\n    with:\n      # This needs to compare to the string `'true'`, because of GHA awkwardness\n      push: ${{ needs.rules.outputs.devcontainer-push == 'true' }}\n\n  test-msrv:\n    runs-on: ubuntu-24.04\n    needs: rules\n    if: needs.rules.outputs.nightly == 'true'\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-msrv\n          # TODO: remove this version pinning\n          # The latest 0.16 supports workspace inheritance, so the check will fail\n          version: \"0.15\"\n        # Note this currently uses a manually maintained key in\n        # `prqlc/prqlc/Cargo.toml`, because of\n        # https://github.com/foresterre/cargo-msrv/issues/590\n      - name: Verify minimum rust version — prqlc\n        # Ideally we'd check all crates, ref https://github.com/foresterre/cargo-msrv/issues/295\n        working-directory: prqlc/prqlc\n        run: cargo msrv verify\n\n  test-deps-min-versions:\n    runs-on: ubuntu-24.04\n    needs: rules\n    if: needs.rules.outputs.nightly == 'true'\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - run: rustup override set nightly-2025-11-10\n      - uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-hack\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cargo-minimal-versions\n      - run: ./.github/workflows/scripts/set_version.sh\n      - name: 💰 Cache\n        uses: Swatinem/rust-cache@v2\n        with:\n          prefix-key: ${{ env.version }}-${{ hashFiles('./Cargo.lock') }}\n          save-if: ${{ github.ref == 'refs/heads/main' }}\n      - name: Verify minimum rust version\n        run: cargo minimal-versions test --direct\n\n  check-ok-to-merge:\n    # This indicates to GitHub whether everything in this workflow has passed\n    # and (unlike if we included each task in the branch's GitHub required\n    # tests) will pass when a task is skipped.\n    if: always()\n    needs:\n      - build-devcontainer\n      - build-web\n      - check-links-book\n      - check-links-markdown\n      - lint-megalinter\n      - nightly\n      - publish-web\n      - test-deps-min-versions\n      - test-dotnet\n      - test-elixir\n      - test-grammars\n      - test-java\n      - test-js\n      - test-msrv\n      - test-php\n      - test-prqlc-c\n      - test-python\n      - test-rust\n      - test-rust-main\n      # - test-taskfile  # Disabled due to https://github.com/PRQL/prql/pull/4876\n    runs-on: ubuntu-24.04\n    steps:\n      - name: Decide whether the needed jobs succeeded or failed\n        # https://github.com/re-actors/alls-green/issues/23\n        uses: re-actors/alls-green@cf9edfcf932a0ed6b431433fa183829c68b30e3f\n        with:\n          jobs: ${{ toJSON(needs) }}\n          # We don't include `check-links-markdown`, since occasionally we'll want to merge\n          # something which temporarily fails that, such as if we're changing the\n          # location of a file in this repo which is linked to.\n          #\n          # We're currently including `nightly` because I'm not sure whether\n          # it's always reliable; e.g. `cargo-audit`\n          allowed-failures: |\n            [\n              \"check-links-markdown\",\n              \"nightly\"\n            ]\n          # We skip jobs deliberately, so we are OK if any are skipped.\n          #\n          # Copy-pasted from `needs`, since it needs to be a json list, so `${{\n          # toJSON(needs) }}` (which is a map) doesn't work.\n          # https://github.com/re-actors/alls-green/issues/23\n          allowed-skips: |\n            [\n              \"build-devcontainer\",\n              \"build-web\",\n              \"check-links-book\",\n              \"check-links-markdown\",\n              \"lint-megalinter\",\n              \"nightly\",\n              \"publish-web\",\n              \"test-deps-min-versions\",\n              \"test-dotnet\",\n              \"test-elixir\",\n              \"test-grammars\",\n              \"test-java\",\n              \"test-js\",\n              \"test-msrv\",\n              \"test-php\",\n              \"test-prqlc-c\",\n              \"test-python\",\n              \"test-rust\",\n              \"test-rust-main\",\n              \"test-taskfile\"\n            ]\n\n  build-prqlc:\n    runs-on: ${{ matrix.os }}\n    needs: rules\n    if: needs.rules.outputs.rust == 'true' || needs.rules.outputs.main == 'true'\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          # Match the features with the available caches from tests\n          - os: ubuntu-24.04\n            target: x86_64-unknown-linux-musl\n            features: default\n          # TODO: Until we have tests for these, we don't have a cache for them.\n          # If we can add tests, then re-enable them. They run on `release.yaml`\n          # regardless.\n          #\n          # - os: ubuntu-24.04\n          #   target: aarch64-unknown-linux-musl\n          - os: macos-15\n            target: aarch64-apple-darwin\n            features: default,test-dbs\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n            features: default\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: ./.github/actions/build-prqlc\n        with:\n          target: ${{ matrix.target }}\n          profile: dev\n          features: ${{ matrix.features }}\n    # We need consistent env vars across all workflows for the cache to work\n    env:\n      CARGO_TERM_COLOR: always\n      CLICOLOR_FORCE: 1\n      RUSTFLAGS: \"-C debuginfo=0\"\n      RUSTDOCFLAGS: \"-Dwarnings\"\n\n  build-prqlc-c:\n    runs-on: ${{ matrix.os }}\n    needs: rules\n    if: needs.rules.outputs.main == 'true'\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          # Match the features with the available caches from tests\n          - os: ubuntu-24.04\n            target: x86_64-unknown-linux-musl\n            features: default\n          - os: macos-15\n            target: aarch64-apple-darwin\n            features: default,test-dbs\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n            features: default\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: ./.github/actions/build-prqlc-c\n        with:\n          target: ${{ matrix.target }}\n          profile: dev\n          features: ${{ matrix.features }}\n    # We need consistent env vars across all workflows for the cache to work\n    env:\n      CARGO_TERM_COLOR: always\n      CLICOLOR_FORCE: 1\n      RUSTFLAGS: \"-C debuginfo=0\"\n      RUSTDOCFLAGS: \"-Dwarnings\"\n\n  create-issue-on-nightly-failure:\n    runs-on: ubuntu-24.04\n    needs:\n      - check-ok-to-merge\n      - rules\n    if:\n      ${{ always() && contains(needs.*.result, 'failure') &&\n      needs.rules.outputs.nightly-upstream == 'true' }}\n    permissions:\n      contents: read\n      issues: write\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: JasonEtco/create-an-issue@v2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          LINK:\n            ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{\n            github.run_id }}\n        with:\n          filename: .github/nightly-failure.md\n          update_existing: true\n          search_existing: open\n\n  update-rust-toolchain:\n    runs-on: ubuntu-24.04\n    needs: rules\n    if: ${{ needs.rules.outputs.nightly-upstream == 'true' }}\n    # Note that this doesn't change the minimum supported version, only the\n    # default toolchain to run on. The minimum is defined by Cargo.toml's\n    # metadata.msrv and is updated manually based on when build environments\n    # such as debian & winget are updated.\n    steps:\n      - name: 📂 Checkout code\n        uses: actions/checkout@v5\n      - uses: a-kenji/update-rust-toolchain@main\n        with:\n          # Discussion in #1561\n          minor-version-delta: 1\n          toolchain-path: \"./rust-toolchain.toml\"\n          pr-title: \"build: Update rust toolchain version\"\n          token: ${{ secrets.PRQL_BOT_GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n\ndist\ntarget*/\n*.out\n*.log\n\nlcov.info\n*.profraw\n\n**/.vscode/settings.json\n**/.vscode/launch.json\n**/.vscode/tasks.json\n.idea\n\n_*.prql\n/*.prql\n/*.sql\n_*.sql\n\n# Appears when using Docker container\n.Trash-0\n\n**/node_modules/\n\n.task\n.direnv\n\n*.snap.tmp\n# These shouldn't be committed, and cause watchers to re-run when they're\n# created, which we don't want. That said, if ignoring them causes confusion\n# (e.g. folks look at their git status to assess whether there are pending\n# snapshots), we can adjust.\n*.pending-snap\n*.snap.new\n\n*.my.csv\nlog*.json\nlog*.html\n\ncobertura.xml\ncodecov.json\n.envrc\n\n# Some paths we don't want to include. We could ignore these further down the\n# tree, but prettier relies on this file rather than resolving all the way down.\n.venv\n.nox\nvendor\n.mypy_cache/\n.hypothesis/\n.aider*\n.env\n\n# Lychee link checker cache\n.lycheecache\n"
  },
  {
    "path": ".markdownlint-cli2.yaml",
    "content": "config:\n  # We use prettier for line length & wrapping\n  MD013: false\n  # Code block style — generally this is fine, but it has false positives on\n  # footnotes. See below re markdown-it-footnote issue. If we could resolve\n  # that, we could turn this back on.\n  MD046: false\n# markdownItPlugins:\n#\n# Doesn't seem to help with some issues — if someone wants to help resolve then\n# great, but it's no huge stress to have some false positives.\n#\n# https://github.com/DavidAnson/markdownlint/issues/689\n#   - [\"markdown-it-footnote\"]\n"
  },
  {
    "path": ".mega-linter.yaml",
    "content": "GITHUB_COMMENT_REPORTER: false\nDISABLE:\n  - RUST\n  - JAVASCRIPT\n  - PYTHON\nDISABLE_LINTERS:\n  - SPELL_CSPELL\n  - CSS_STYLELINT\n  - PHP_PSALM\n  - PHP_PHPSTAN # Disabled for now as we couldn't figure out how to prevent false positives. #2069\n  - SQL_TSQLLINT\n  - REPOSITORY_KICS\n  - SPELL_LYCHEE # Throwing network errors. We already check link in other GH actions.\n  - MARKDOWN_MARKDOWN_LINK_CHECK # Slow (40+ seconds). We already check links in other GH actions.\n  - REPOSITORY_TRUFFLEHOG # Detecting secrets in .git/config, which is not even committed.\n  - REPOSITORY_GRYPE # Slow (10+ seconds). Blocking unrelated PRs. We already have depandabot.\n  - YAML_V8R # Slow (70+ seconds). We don't use YAML schema.\n  - JSON_V8R # Failing for vscode-style syntax (comments).\n  - REPOSITORY_GITLEAKS # False positive on codecov token, which according to codecov is fine to have hardcoded\nDISABLE_ERRORS_LINTERS:\n  - COPYPASTE_JSCPD\n  - REPOSITORY_TRIVY\n  # Long system prompt line in claude.yaml exceeds yamllint's 500 char limit\n  - YAML_YAMLLINT\n  - REPOSITORY_CHECKOV\n  - REPOSITORY_DEVSKIM\n  - BASH_SHELLCHECK\n  - C_CPPLINT\n  - CPP_CPPLINT\n  - DOCKERFILE_HADOLINT\n  - HTML_DJLINT\n  - HTML_HTMLHINT\n  - JAVA_CHECKSTYLE\n  - JAVA_PMD\n  - JSON_JSONLINT\n  - MAKEFILE_CHECKMAKE\n  - MARKDOWN_MARKDOWN_LINK_CHECK\n  # Prevents us from starting a new library, since it raises an error on unpublished libraries. Can remove after publishing...\n  - REPOSITORY_DUSTILOCK\n  - SPELL_MISSPELL\n  # Disabled for now, as @max-sixty didn't know whether \"Unable to locate the\n  # project file. A project file (tsconfig.json or tsconfig.eslint.json) is\n  # required in order to use ts-standard.\" was worth fixing, from #3608. Happy\n  # for someone more informed to turn it back on.\n  - TYPESCRIPT_STANDARD\n  # Disabled for now, as @max-sixty didn't know how to fix\n  # `./prqlc/bindings/php/tests/CompilerTest.php (trailing_comma_in_multiline)`.\n  # Fine for someone else to take a look.\n  - PHP_PHPCSFIXER\nPHP_PHPCS_ARGUMENTS:\n  - --standard=PSR12\nRAKU_RAKU_ARGUMENTS: -I ./grammars/raku/lib/\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v6.0.0\n    hooks:\n      - id: end-of-file-fixer\n        exclude: '(.*\\.snap|.*render-link.html|head.hbs)'\n      - id: check-yaml\n      - id: mixed-line-ending\n      - id: trailing-whitespace\n        # rustfmt handles rust files, and in some snapshots we expect trailing spaces.\n        exclude: '.*\\.(rs|snap)$'\n  - repo: https://github.com/crate-ci/typos\n    rev: v1\n    hooks:\n      - id: typos\n        # https://github.com/crate-ci/typos/issues/347\n        pass_filenames: false\n  - repo: https://github.com/rbubley/mirrors-prettier\n    rev: v3.8.1\n    hooks:\n      - id: prettier\n        additional_dependencies:\n          - prettier\n          # TODO: This doesn't seem to work, would be great to fix.\n          # https://github.com/PRQL/prql/issues/3078\n          - prettier-plugin-go-template\n  - repo: https://github.com/astral-sh/ruff-pre-commit\n    rev: v0.15.6\n    hooks:\n      - id: ruff\n        args: [--fix]\n      - id: ruff-format\n  - repo: https://github.com/pre-commit/mirrors-clang-format\n    rev: v22.1.1\n    hooks:\n      - id: clang-format\n        types_or: [c, c++]\n  - repo: https://github.com/r0x0d/pre-commit-rust\n    rev: v1.0.1\n    hooks:\n      - id: fmt\n  - repo: https://github.com/r0x0d/pre-commit-rust\n    rev: v1.0.1\n    hooks:\n      - id: clippy\n        stages: [manual]\n  - repo: https://github.com/rhysd/actionlint\n    rev: v1.7.11\n    hooks:\n      - id: actionlint\n  # Link checker using local hooks with language: rust\n  # Pre-commit will automatically install lychee via cargo\n  - repo: local\n    hooks:\n      - id: lychee\n        name: lychee (local links only)\n        entry: lychee\n        language: rust\n        additional_dependencies: [\"cli:lychee:0.20.1\"]\n        files: \\.(md|rst)$\n        exclude: ^web/website/themes/\n        args:\n          - --no-progress\n          # For PRs: whitelist PRQL domains and local files only\n          # Pattern matches:\n          #   - github.com/PRQL/* and github.com/prql/*\n          #   - prql-lang.org/*\n          #   - raw.githubusercontent.com/PRQL/* and raw.githubusercontent.com/prql/*\n          #   - file://* (local/relative links)\n          # All other external links are skipped for faster PR checks\n          - --include=^(https://(github\\.com/(PRQL|prql)|prql-lang\\.org|raw\\.githubusercontent\\.com/(PRQL|prql))|file://)\n      - id: lychee-all\n        name: lychee-all (all links)\n        entry: lychee\n        language: rust\n        additional_dependencies: [\"cli:lychee:0.20.1\"]\n        stages: [manual]\n        files: \\.(md|rst)$\n        exclude: ^web/website/themes/\n        args: [\"--config=.config/lychee.toml\", \"--no-progress\"]\n  - repo: local\n    hooks:\n      - id: no-dbg\n        name: no-dbg\n        description: We shouldn't merge code with `dbg!` in\n        language: pygrep\n        types: [\"rust\"]\n        entry: \"dbg!\"\n  - repo: local\n    hooks:\n      - id: prql-codeblock\n        name: Prevent prql codeblocks evaluating in book\n        description:\n          prql code blocks are evaluated and replaced in the book; instead use\n          `prql no-eval`\n        language: pygrep\n        entry: \"```prql$\"\n        files: 'CHANGELOG\\.md$'\n\n  # This is quite strict, and doesn't fix a large enough share of the issues it\n  # finds, so we don't include it. But it's reasonable to run every now & again\n  # manually and take its fixes.\n  #\n  # - repo: https://github.com/DavidAnson/markdownlint-cli2\n  #   rev: v0.5.1\n  #   hooks:\n  #     - id: markdownlint-cli2\n  #       args: [\"--fix\"]\n  #       additional_dependencies:\n  #         - markdown-it-footnote\n\nci:\n  # Currently network access isn't supported in the CI product.\n  skip: [fmt, lychee, lychee-all]\n  autoupdate_commit_msg: \"chore: pre-commit autoupdate\"\n"
  },
  {
    "path": ".prettierignore",
    "content": "# prettier respects the root `.gitignore` file, so prefer that for files which\n# we also want to ignore from our version control. This should contain files we\n# want to track but not format.\n\n**/*.rs\n**/*.min.*\n\n/web/book/theme/highlight.js\n/web/playground/build\n/web/website/public\n/web/book/book\n\n# TODO: move these into content out of layouts\n/web/website/themes/prql-theme/layouts/_default/_markup/render-link.html\n\n# TODO: fix the go-template issue in `.prettierrc.yaml`\n**/*.html\n"
  },
  {
    "path": ".prettierrc.yaml",
    "content": "proseWrap: always\n# TODO: fix\n# overrides:\n#   # https://github.com/NiklasPor/prettier-plugin-go-template\n#   - files:\n#       - \"*.html\"\n#     options:\n#       parser: \"go-template\"\n"
  },
  {
    "path": ".sqlfluff",
    "content": "[sqlfluff]\ndialect = ansi\nexclude_rules = references.keywords\n"
  },
  {
    "path": ".typos.toml",
    "content": "[files]\nextend-exclude = [\n  \"web/book/theme/highlight.js\",\n  \"prqlc/prqlc/tests/integration/data/\",\n  \"prqlc/prqlc/tests/integration/snapshots/\",\n  \"web/website/themes/prql-theme/static/plugins/bootstrap\",\n  \"web/website/themes/prql-theme/static/plugins/highlight/highlight.min.js\",\n]\n\n[default.extend-words]\n# <itemDatas> in grammars/KSyntaxHighlighting/prql.xml\ndatas = \"datas\"\n# Java test framework\ntestng = \"testng\"\n# Readme highlighting the first characters of these words\nanguage = \"anguage\"\nelational = \"elational\"\nipelined = \"ipelined\"\nuery = \"uery\"\n# `wee-alloc` is a crate name\nwee = \"wee\"\nflate = \"flate\"\n# distinct_ons is correct (SQL DISTINCT ON)\nons = \"ons\"\n# SQL UNIONs - typos incorrectly thinks UNIO should be UNION\n# This happens because typos tokenizes UNIONs and finds UNIO\nUNIO = \"UNIO\"\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    // Keep in sync with Taskfile.yaml\n    \"prql-lang.prql-vscode\",\n    \"rust-lang.rust-analyzer\",\n    \"mitsuhiko.insta\",\n    \"esbenp.prettier-vscode\",\n    \"budparr.language-hugo-vscode\"\n  ]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# PRQL Changelog\n\n## [unreleased]\n\n**Language**:\n\n**Features**:\n\n**Fixes**:\n\n**Documentation**:\n\n**Web**:\n\n**Integrations**:\n\n**Internal changes**:\n\n**New Contributors**:\n\n## 0.13.11 — 2026-03-19\n\n0.13.11 has 75 commits from 7 contributors. Selected changes:\n\n**Features**:\n\n- Add support for `date.to_text` for BigQuery (@segv, #5712)\n\n**Fixes**:\n\n- Prevent panic on bare `*` in `select` (@max-sixty, #5696)\n- Handle partial application of transforms in user-defined functions\n  (@max-sixty, #5663; reported by @Rafferty97)\n- Keep Computes with Aggregate when Filter follows (@max-sixty, #5639; reported\n  by @matiastoro)\n- Preserve sort before take in CTE (@max-sixty, #5638; reported by @lukapeschke)\n- Preserve sort with empty group `{}` (@max-sixty, #5635)\n- Return proper error for bare lambda expressions (@max-sixty, #5634)\n- Prevent DISTINCT ON internal sorting from leaking past joins (@max-sixty,\n  #5633; reported by @annashmatko)\n- Fix invalid MSSQL SQL when DISTINCT and FETCH are combined (@max-sixty, #5630)\n\n**Documentation**:\n\n- Editorial tweaks (@richb-hanover, #5645)\n\n**Integrations**:\n\n- [micro](https://micro-editor.github.io/) 2.0.15 has syntax highlighting for\n  PRQL. (@vanillajonathan)\n\n**Internal changes**:\n\n- Use chumsky 0.12.0 for all targets (#5659)\n- Update rust toolchain version (#5673)\n- Fix mdbook admonition syntax for mdBook 0.5 native support (#5649)\n\n**New Contributors**:\n\n- @segv, with #5712\n\n## 0.13.10 — 2025-12-16\n\n0.13.10 completes the npm OIDC publishing fix from 0.13.9.\n\n**Features**:\n\n- Add `read_json` support for DuckDB and ClickHouse (@adamnemecek, #5608)\n\n**Fixes**:\n\n- Fix `text.length` on Snowflake (@priithaamer, #5614)\n\n**Internal changes**:\n\n- Fix npm OIDC publishing by upgrading to Node 24 (@max-sixty, #5619)\n- Fix push-web-branch workflow permissions (@max-sixty, #5620)\n\n## 0.13.9 — 2025-12-12\n\n0.13.9 is a re-release of 0.13.8 with fixed npm publishing.\n\n**Internal changes**:\n\n- Use OIDC for npm publishing (@max-sixty, #5609)\n\n## 0.13.8 — 2025-12-12\n\n0.13.8 has 47 commits from 6 contributors. Selected changes:\n\n**Features**:\n\n- Add named parameter support for DuckDB read_parquet (@max-sixty, #5563)\n\n**Fixes**:\n\n- Snowflake interval quoting style (@priithaamer, #5604)\n- Return error instead of panicking when window rows/range is not a range\n  (@max-sixty, #5603)\n- Add ORDER BY fallback for Snowflake window functions (@max-sixty, #5583)\n- DISTINCT ON to include wildcard when projection is empty (@max-sixty, #5562)\n- Lineage traces through CTEs to source tables (@nightscape, #5581)\n- Lineage works with unions (@nightscape, #5550)\n- Handle lineage correctly when group pipeline simplifies to non-TransformCall\n  (@max-sixty, #5584)\n- Return error for join referencing inaccessible table (@max-sixty, #5587)\n- Error when table variable used in scalar context (@max-sixty, #5585)\n- Improve error messages for missing main pipeline (@max-sixty, #5565)\n- Provide clear error for empty tuple/array in from (@max-sixty, #5564)\n- Improve error message for negative numbers in transforms (@max-sixty, #5566)\n\n**Documentation**:\n\n- Close HTML comment in sort.md documentation (@max-sixty, #5597)\n\n**Integrations**:\n\n- Add grammar file for Raku (@vanillajonathan, #5576)\n\n**Internal changes**:\n\n- Upgrade mdbook to 0.5.0 (#5568)\n- Update macOS runners to version 15 (#5596)\n- Remove 2nd person from error messages (#5567)\n\n**New Contributors**:\n\n- @nightscape, with #5550\n\n## 0.13.7 — 2025-11-08\n\n0.13.7 has 8 commits from 6 contributors. Selected changes:\n\n**Fixes**:\n\n- Fix INTERVAL quoting in Redshift (@lukapeschke, #5545)\n- Fix support for text.contains in sql.redshift target (@dimtion, #5549)\n- Fix division in Redshift produces float (@priithaamer, #5546)\n- Use || operator over CONCAT for std.concat in Redshift (@lukapeschke, #5540)\n\n**Web**:\n\n- Bump vite from 7.1.11 to 7.2.0 in playground (#5547)\n\n## 0.13.6 — 2025-11-01\n\n0.13.6 has 40 commits from 6 contributors. Selected changes:\n\n**Features**:\n\n- Add support for Redshift dialect (@priithaamer, #5537)\n\n**Fixes**:\n\n- Fix append regression in 0.13.5 (@priithaamer, #5495)\n- Fix s-string escaping (@priithaamer, #5497)\n- Filter out attestation manifests in platform verification (@max-sixty, #5509)\n\n**Internal changes**:\n\n- Migrate devcontainer build from QEMU to native ARM64 runners (@max-sixty,\n  #5506)\n- Migrate from markdown-link-check to lychee (@max-sixty, #5519)\n- Use zero-copy slice for binary/hex/octal number parsing (perf improvement,\n  @max-sixty, #5488)\n- Update rust toolchain version (#5536)\n- Do not allow incompatible rust-version dependencies (@eitsupi, #5493)\n- Pin typos to v1.37.2 (@max-sixty, #5510)\n\n## 0.13.5 — 2025-10-09\n\n0.13.5 has 237 commits from 14 contributors. Selected changes:\n\n**Features**:\n\n- Support for SQL arrays as `s[...]` syntax (@Robert Valek, #5312)\n- Extract SQL column names from s-string tables when possible (@lukapeschke,\n  #5310)\n\n**Fixes**:\n\n- Sort step before an aggregate step no longer requires its columns to avoid a\n  group by clause error (@julien-pinchelimouroux, #5347)\n- Always add quotes on identifiers for Snowflake dialect\n  (@julien-pinchelimouroux, #5461)\n- Join with table containing column named \"source\" now works correctly (@Priit\n  Haamer, #5468)\n- Columns required by sorting are properly redirected (@lukapeschke, #5464)\n- Ensure sorts are done on columns of the right table (@lukapeschke, #5338)\n- Deduplicate selected items in gen_projection (@lukapeschke, #5305)\n- Handle complex append cases (@Elouan Poupard-Cosquer, #5366)\n- Improve requirement logic (@Elouan Poupard-Cosquer, #5357)\n- Avoid type mismatch with Postgres in append (@Elouan Poupard-Cosquer, #5343)\n- Apply column order on CTEs in append (@Elouan Poupard-Cosquer, #5323)\n\n**Documentation**:\n\n- Fix binary literal example (@ftsfranklin, #5475)\n- Use correct table in grouping tutorial (@fnuttens, #5332)\n\n**Integrations**:\n\n- TEA 63.3.1, a Qt-based text editor has syntax highlighting for PRQL\n  (@vanillajonathan, #5220)\n- Micro text editor grammar is now upstream (@vanillajonathan, #5353)\n- Add LSP stub (@vanillajonathan, #5197)\n\n**Internal changes**:\n\n- Upgrade parser and lexer to chumsky 0.11, providing a 7x performance\n  improvement (#5223, #5476, #5477)\n- Set Rust linker on win64, fix build crash (@kgutwin, #5345)\n- Integration tests compile all dialects and diff (@kgutwin, #5344)\n\n**New Contributors**:\n\n- @Elouan Poupard-Cosquer, with #5366\n- @Priit Haamer, with #5468\n- @Robert Valek, with #5312\n- @fnuttens, with #5332\n- @ftsfranklin, with #5475\n- @julien-pinchelimouroux, with #5347\n\n## 0.13.4 — 2025-03-26\n\n0.13.4 is a small bugfix release.\n\nIt has 57 commits from 10 contributors. Selected changes (in particular, a few\nbugfixes aren't listed here):\n\n**Integrations**:\n\n- Add syntax highlight file for KSyntaxHighlighting. (@vanillajonathan, #5177)\n- Add syntax highlight file for Vim. (@vanillajonathan, #5185)\n- Add syntax highlight file for GNU Emacs. (@vanillajonathan, #5189)\n- [Kakoune](https://kakoune.org/), a terminal-based text editor has syntax\n  highlighting for PRQL. (@vanillajonathan)\n- [Neovim](https://neovim.io/) 0.11 has syntax highlighting for PRQL.\n  (@vanillajonathan)\n\n## 0.13.3 — 2025-01-25\n\n0.13.3 is a small release containing a few bug fixes and improvements. It has 86\ncommits from 10 contributors. Selected changes:\n\n**Fixes**:\n\n- Sort steps in sub-pipelines no longer cause a column lookup error\n  (@lukapeschke, #5066)\n- Dereferencing of sort columns when rendering SQL now done in context of main\n  pipeline (@kgutwin, #5098)\n\n**New Contributors**:\n\n- @lukapeschke, with #5066\n\n## 0.13.2\n\n0.13.2 is a tiny release to fix an issue publishing 0.13.1 to crates.io.\n\n## 0.13.1\n\n0.13.1 is a small release containing a few bug fixes and improvements. Velocity\nhas slowed down a bit in recent months, we're still hoping to finish the new\nresolver and the new formatter in the near future.\n\nIt has 97 commits from 10 contributors. Selected changes:\n\n**Features**:\n\n- Add a option to the experimental documentation generator to output the docs in\n  HTML format. The option is given using the `--format=html` option.\n  (@vanillajonathan, 4791)\n\n- The version of the library is now read from `git describe`. This doesn't\n  affect libraries built on git tags (such as our releases), only those built\n  when developing. When reporting bugs, this helps identify the exact version.\n  (@max-sixty & @m-span, #4804)\n\n**Fixes**:\n\n- Raw strings (`r\"...\"`) are retained through `prqlc fmt` (@max-sixty, #4848)\n\n- Strings containing an odd contiguous number of quotes are now delimited by an\n  odd number of quotes when being formatted. The previous implementation would\n  use an even number, which is invalid PRQL. (@max-sixty, #4850)\n\n- A few more keywords are quoted, such as `user`, which is a reserved keyword in\n  PostgreSQL. (@max-sixty)\n\n## 0.13.0 — 2024-07-25\n\n0.13.0 brings a new debug logging framework, a big refactor of the parser, a new\nhighlighter, an `**` operator for exponentiation, a few bug fixes, and lots of\nother changes. It has 153 commits from 11 contributors.\n\nOur work continues on rewriting the resolver and completing `prqlc fmt`.\n\nSelected changes:\n\n**Language**:\n\n- Parentheses are always required around pipelines, even within tuples. For\n  example:\n\n  ```prql no-eval\n  from artists\n  # These parentheses are now required\n  derive {a=(b | math.abs)}\n  # No change — doesn't affect expressions or function calls without pipelines\n  derive {x = 3 + 4}\n  ```\n\n  This is a small breaking change. The new behavior matches the existing\n  documentation. (@max-sixty, #4775)\n\n- A new `**` operator for exponentiation. (@aljazerzen & @max-sixty, #4125)\n\n**Features**:\n\n- `prqlc compile --debug-log=log.html` will generate an HTML file with a\n  detailed log of the compilation process. (@aljazerzen, #4646)\n- Added `prqlc debug json-schema` command to auto-generate JSON Schema\n  representations of commonly exposed IR types such as PL and RQ. (@kgutwin,\n  #4698)\n- Add documentation comments to the output of the documentation generator.\n  (@vanillajonathan, #4729)\n- Add CLI syntax highlighting to `prqlc`. You can try it as\n  `prqlc experimental highlight example.prql`. (@vanillajonathan, #4755)\n\n**Fixes**:\n\n- Using `in` with an empty array pattern (e.g. `expr | in []`) will now output a\n  constant `false` condition instead of an `expr IN ()`, which is syntactically\n  invalid in some SQL dialects (@Globidev, #4598)\n\n**Integrations**:\n\n- The Snap package previously released on the edge channel is now released on\n  the stable channel. (@vanillajonathan, #4784)\n\n**Internal changes**:\n\n- Major reorganization of `prqlc-parser` — `prqlc-ast` is merged into\n  `prqlc-parser`, and `prqlc-parser`'s files are rearranged, including its\n  exports. This is part of an effort to modularize the compiler by stage,\n  reducing the amount of context that's required to understand a single stage.\n  There will likely be some further changes (more detail in the PR description).\n  (@m-span, #4634)\n  - This is a breaking change for any libraries that depend on `prqlc-parser`\n    (which should be fairly rare).\n\n- Renamed `prql-compiler-macros` to `prqlc-macros` for consistency with other\n  crates (@max-sixty, #4565)\n\n- `prql-compiler`, the old name for `prqlc`, is removed as a facade to `prqlc`.\n  It had been deprecated for a few versions and will no longer be updated.\n  (@max-sixty)\n\n- New benchmarks (@max-sixty, #4654)\n\n**New Contributors**:\n\n- @Globidev, with #4598\n\n## 0.12.2 — 2024-06-10\n\n0.12.2 is a very small release which renames `prql-js` to `prqlc-js` to match\nour standard naming scheme. Within node the package is imported as `prqlc`.\n\nIt also fixes a mistake in the `prqlc-python` release pipeline.\n\n## 0.12.1 — 2024-06-09\n\n0.12.1 is a tiny hotfix release which fixes how intra-prql crate dependencies\nwere specified.\n\n## 0.12.0 — 2024-06-08\n\n0.12.0 contains a few months of smaller features. Our focus has been on\nrewriting the resolver, an effort that is still ongoing.\n\nIt has 239 commits from 12 contributors. Selected changes (most are not listed\nhere, possibly we should be more conscientious about adding them...):\n\n**Features**:\n\n- Add `prqlc lex` command to the CLI (@max-sixty)\n- Add `prqlc debug lineage` command to the CLI, creating an expression lineage\n  graph from a query (@kgutwin, #4533)\n- Initial implementation of an experimental documentation generator that\n  generates Markdown documentation from `.prql` files. (@vanillajonathan,\n  #4152).\n- Join's `side` parameter can take a reference that resolves to a literal (note:\n  this is an experimental feature which may change in the future) (@kgutwin,\n  #4499)\n\n**Fixes**:\n\n- Support expressions on left hand side of `std.in` operator. (@kgutwin, #4498)\n- Prevent panic for `from {}` and `std` (@m-span, #4538)\n\n**Web**:\n\n- The `browser` dist files are now built with `wasm-pack`'s `web` target. As a\n  result, they should be usable as ES Modules, through JS CDNs, and for example\n  with Observable Framework (@srenatus, #4274).\n\n**Integrations**:\n\n- The syntax highlighter package for Sublime Text is now\n  [published](https://packagecontrol.io/packages/PRQL) (@vanillajonathan).\n- The\n  [VSCode Great Icons](https://marketplace.visualstudio.com/items?itemName=emmanuelbeziat.vscode-great-icons)\n  icon pack extension shows a database icon for `.prql` files. (@EmmanuelBeziat)\n- [Tokei](https://github.com/XAMPPRocky/tokei), a source lines of code counter\n  now has support for `.prql` files. (@vanillajonathan)\n- Add syntax highlight file for the [micro](https://micro-editor.github.io/)\n  text editor. (@vanillajonathan)\n\n**New Contributors**:\n\n- @srenatus, with #4274\n- @jacquayj, with #4332\n- @pdelewski, with #4337\n- @m-span, with #4422\n- @kgutwin, with #4498\n\n## 0.11.4 — 2024-02-25\n\n0.11.4 is a hotfix release, fixing a CI issue that caused the CLI binaries to be\nbuilt without the `cli` feature.\n\n## 0.11.3 — 2024-02-10\n\n0.11.3 is a very small release, mostly a rename of the Python bindings.\n\nThe release has 13 commits from 4 contributors.\n\n**Internal changes**:\n\n- As part of making our names more consistent, the Python bindings are renamed.\n  `prql-python` becomes a package published and importable as `prqlc`. The\n  internal Rust crate is named `prqlc-python`.\n\n## 0.11.2 — 2024-02-07\n\n0.11.2 contains lots of internal changes, lots of syntax highlighting, and the\nbeginning of `lutra`, a query runner.\n\nThis release has 122 commits from 9 contributors. Selected changes:\n\n**Features**:\n\n- Initial implementation of `lutra`, a query runner. (@aljazerzen, #4182, #4174,\n  #4134)\n- `prqlc fmt` works on projects with multiple files. (@max-sixty, #4028)\n\n**Fixes**:\n\n- Reduce stack memory usage (@aljazerzen, #4103)\n\n**Integrations**:\n\n- Add syntax highlight file for GtkSourceView. (@vanillajonathan, #4062)\n- Add syntax highlight file for CotEditor. (@vanillajonathan)\n- Add syntax highlight file for Sublime Text. (@vanillajonathan, #4127)\n- [sloc](https://github.com/flosse/sloc), a source lines of code counter now has\n  support for `.prql` files. (@vanillajonathan)\n\n**Internal changes**:\n\n- `prql-compiler` has been renamed to `prqlc`, and we've established a more\n  consistent naming scheme. The existing crate will still be published,\n  re-exporting `prqlc`, so no dependencies will break. A future version will add\n  a deprecation warning.\n- The `prqlc-clib` crate was renamed to `prqlc-c`, and associated artifacts were\n  renamed. We're trying to make names consistent (ideally for the final time!),\n  and have a plan to rename some other bindings. (@max-sixty, #4077)\n- Add lots of whitespace items to the lexer, in preparation for the completion\n  of `prqlc fmt` (@max-sixty, #4109, #4105)\n- Table declarations (@aljazerzen, #4126)\n\n**New Contributors**:\n\n- @kaspermarstal, with #4124\n\n## 0.11.1 — 2023-12-26\n\n0.11.1 fixes a couple of small bugs; it comes a few days after 0.11.\n\nThis release has 16 commits from 6 contributors. Selected changes:\n\n**Features**:\n\n- Infer the type of array literals to be the union of types of its items.\n  (@aljazerzen, #3989)\n- `prql` module is added and the `prql_version` function is renamed to the\n  `prql.version` function. The old `prql_version` function is deprecated and\n  will be removed in the future release. (@eitsupi, #4006)\n\n**Fixes**:\n\n- Do not compile to `DISTINCT ON` when `take n` is used with `group` for the\n  targets `clickhouse`, `duckdb` and `postgres`. (@PrettyWood, #3988)\n- Fix `take` n rows for `mssql` dialect by switching from TOP to FETCH\n  (@PrettyWood, #3994)\n\n## 0.11.0 — 2023-12-19\n\n0.11.0 introduces new `date`, `text` & `math` modules with lots of standard\nfunctions, including a new `date.to_text` function. It contains a few bugs\nfixes, and lots of internal improvements to the compiler.\n\nThis release has 119 commits from 9 contributors. Selected changes:\n\n**Language**:\n\n- _Breaking_: `group`'s `by` columns are now excluded from the partition.\n  (#3490)\n- _Breaking_: `round` is now in the `math` module and needs to be called via\n  `math.round`. (#3928)\n- _Breaking_: `lower` and `upper` are now in the `text` module and need to be\n  called via `text.lower` and `text.upper`. (#3913, #3973)\n\n**Features**:\n\n- The `std.in` function now supports a list of values (@PrettyWood, #3883)\n- Most standard mathematical functions are now supported: `abs`, `floor`,\n  `ceil`, `pi`, `exp`, `ln`, `log10`, `log`, `sqrt`, `degrees`, `radians`,\n  `cos`, `acos`, `sin`, `asin`, `tan`, `atan`, `pow` and `round`.\\\n  Those functions are in the `math` module (@PrettyWood, #3909, #3916 & 3928)\n- Most standard string functions are now supported: `ltrim`, `rtrim`, `trim`,\n  `length`, `extract`, `replace`. Utility functions `starts_with`, `contains`\n  and `ends_with` are also available.\\\n  Those functions are in the `text` module (@PrettyWood, #3913, #3973)\n- Formatting a date to a text is now available for Clickhouse, DuckDB, MySQL,\n  MSSQL and Postgres. A new `date` module has been added with the `to_text`\n  function (@PrettyWood, #3951, #3954 & #3955)\n\n**Fixes**:\n\n- Fix an issue with arithmetic precedence (@max-sixty, #3846)\n- `+` and `-` can be used after a cast (@PrettyWood, #3923)\n- The [Lezer](https://lezer.codemirror.net/) grammar had plenty of improvements\n  and fixes. (@vanillajonathan)\n\n**Web**:\n\n- The Playground now uses [Vite](https://vitejs.dev/). (@vanillajonathan)\n\n**Internal changes**:\n\n- Bump `prql-compiler`'s MSRV to 1.70.0 (@eitsupi, #3876)\n\n**New Contributors**:\n\n- @PrettyWood, with #3883\n\n## 0.10.1 — 2023-11-14\n\n0.10.1 is a small release containing some internal fixes of the compiler.\n\nThis release has 36 commits from 7 contributors. Selected changes:\n\n**Features**:\n\n- The `std.sql.read_csv` function and the `std.sql.read_parquet` function\n  supports the `sql.glaredb` target. (@eitsupi, #3749)\n\n**Fixes**:\n\n- Fix the bug of compiling to `DISTINCT ON` when `take 1` is used with\n  `group by` for the targets `sql.clickhouse`, `sql.duckdb` and `sql.postgres`.\n  (@aljazerzen, #3792)\n\n**Integrations**:\n\n- Enable integration tests for GlareDB. (@eitsupi, #3749)\n- [trapd00r/LS_COLORS](https://github.com/trapd00r/LS_COLORS), a collection of\n  LS_COLORS definitions colorizes `.prql` files. (@vanillajonathan)\n- [vivid](https://github.com/sharkdp/vivid), a themeable LS_COLORS generator\n  colorizes `.prql` files. (@vanillajonathan)\n- [colorls](https://github.com/athityakumar/colorls), displays `.prql` files\n  with a database icon. (@vanillajonathan)\n- [Emoji File Icons](https://marketplace.visualstudio.com/items?itemName=mightbesimon.emoji-icons),\n  a VS Code extension displays `.prql` files with a database emoji icon.\n  (@vanillajonathan)\n- [eza](https://eza.rocks/), a modern ls replacement colorizes `.prql` files.\n  (@vanillajonathan)\n- [lsd](https://github.com/lsd-rs/lsd), next gen ls command displays `.prql`\n  files with a database icon. (@vanillajonathan)\n\n## 0.10.0 — 2023-10-26\n\n0.10.0 contains lots of small improvements, including support for new types of\nliteral notation, support for `read_*` functions in more dialects, playground\nimprovements, and a better Lezer grammar (which we're planning on using for a\nJupyter extension).\n\nThis release has 155 commits from 9 contributors. Selected changes:\n\n**Language**:\n\n- _Breaking:_ Case syntax now uses brackets `[]` rather than braces `{}`. To\n  convert previous PRQL queries to this new syntax simply change `case { ... }`\n  to `case [ ... ]`. (@AaronMoat, #3517)\n\n**Features**:\n\n- _Breaking_: The `std.sql.read_csv` function is now compiled to `read_csv` by\n  default. Please set the target `sql.duckdb` to use the DuckDB's\n  `read_csv_auto` function as previously. (@eitsupi, #3599)\n- _Breaking_: The `std.every` function is renamed to `std.all` (@aljazerzen,\n  #3703)\n- The `std.sql.read_csv` function and the `std.sql.read_parquet` function\n  supports the `sql.clickhouse` target. (@eitsupi, #1533)\n- Add `std.prql_version` function to return PRQL version (@hulxv, #3533)\n- A new type `anytype` is added. (@aljazerzen, #3703)\n- Add support for hex escape sequences in strings. Example `\"Hello \\x51\"`.\n  (@vanillajonathan, #3568)\n- Add support for long Unicode escape sequences. Example `\"Hello \\u{01F422}\"`.\n  (@vanillajonathan, #3569)\n- Add support for binary numerical notation. Example\n  `filter status == 0b1111000011110000`. (@vanillajonathan, #3661)\n- Add support for hexadecimal numerical notation. Example\n  `filter status == 0xff`. (@vanillajonathan, #3654)\n- Add support for octal numerical notation. Example `filter status == 0o777`.\n  (@vanillajonathan, #3672)\n- New compile target `sql.glaredb` for [GlareDB](https://docs.glaredb.com/) and\n  integration tests for it (However, there is a bug in the test and it is\n  currently not running). (@universalmind303, @scsmithr, @eitsupi, #3669)\n\n**Web**:\n\n- Allow cmd-/ (Mac) or ctrl-/ (Windows) to toggle comments in the playground\n  editor (@AaronMoat, #3522)\n\n- Limit maximum height of the playground editor's error panel to avoid taking\n  over whole screen (@AaronMoat, #3524)\n\n- The playground now uses [Vite](https://vitejs.dev/) (@vanillajonathan).\n\n**Integrations**:\n\n- Add a CLI command `prqlc collect` to collect a project's modules into a single\n  file (@aljazerzen, #3739)\n- Add a CLI command `prqlc debug expand-pl` to parse & and expand into PL\n  without resolving (@aljazerzen, #3739)\n- Bump `prqlc`'s MSRV to 1.70.0 (@eitsupi, #3521)\n- [Pygments](https://pygments.org/), a syntax highlighting library now has\n  syntax highlighting for PRQL. (@vanillajonathan, #3564)\n- [chroma](https://github.com/alecthomas/chroma), a syntax highlighting library\n  written in Go and used by the static website generator\n  [Hugo](https://gohugo.io/). (@vanillajonathan, #3597)\n- [scc](https://github.com/boyter/scc), a source lines of code counter now has\n  support for `.prql` files. (@vanillajonathan)\n- [gcloc](https://github.com/JoaoDanielRufino/gcloc) a source lines of code\n  counter now has support for `.prql` files. (@vanillajonathan)\n- [cloc](https://github.com/AlDanial/cloc) a source lines of code counter now\n  has support for `.prql` files. (@AlDanial)\n- [gocloc](https://github.com/hhatto/gocloc) a source lines of code counter now\n  has support for `.prql` files. (@vanillajonathan)\n- [The Quarto VS Code extension](https://marketplace.visualstudio.com/items?itemName=quarto.quarto)\n  supports editing PRQL code blocks\n  ([`prqlr`](https://prql-lang.org/book/project/bindings/r.html) is required to\n  render Quarto Markdown with PRQL code blocks). (@jjallaire)\n\n**Internal**:\n\n- Rename some of the internal crates, and refactored their paths in the repo.\n  (@aljazerzen, #3683).\n- Add a `justfile` for developers who prefer that above our `Taskfile.yaml`\n  (@aljazerzen, #3681)\n\n**New Contributors**:\n\n- @hulxv, with #3533\n- @AaronMoat, with #3522\n- @jangorecki, with #3634\n\n## 0.9.5 — 2023-09-16\n\n0.9.5 adds a line-wrapping character, fixes a few bugs, and improves our CI. The\nrelease has 77 commits from 8 contributors. Selected changes are below.\n\nLook out for some conference talks coming up over the next few weeks, including\n[QCon SF on Oct 2](https://qconsf.com/presentation/oct2023/prql-simple-powerful-pipelined-sql-replacement)\nand\n[date2day on Oct 12](https://www.data2day.de/veranstaltung-21353-0-prql-a-modern-language-for-data-transformation.html).\n\n**Language**:\n\n- A new line-wrapping character, for lines that are long and we want to break up\n  into multiple physical lines. This is slightly different from from many\n  languages — it's on the subsequent line:\n\n  ```prql no-eval\n  from artists\n  select is_europe =\n  \\ country == \"DE\"\n  \\ || country == \"FR\"\n  \\ || country == \"ES\"\n  ```\n\n  This allows for easily commenting out physical lines while maintaining a\n  correct logical line; for example:\n\n  ```diff\n  from artists\n  select is_europe =\n  \\ country == \"DE\"\n  \\ || country == \"FR\"\n  \\ || country == \"FR\"\n  -\\ || country == \"ES\"\n  +#\\ || country == \"ES\"\n  ```\n\n  (@max-sixty, #3408)\n\n**Fixes**:\n\n- Fix stack overflow on very long queries in Windows debug builds (@max-sixty,\n  #2908)\n\n- Fix panic when unresolved lineage appears in group or window (@davidot, #3266)\n\n- Fix a corner-case in handling precedence, and remove unneeded parentheses in\n  some outputs (@max-sixty, #3472)\n\n**Web**:\n\n- Compiler panics are now printed to the console (@max-sixty, #3446)\n\n**Integrations**:\n\n- [Ace](https://ace.c9.io/), the JavaScript code editor now has syntax\n  highlighting for PRQL. (@vanillajonathan, #3493)\n\n**Internal changes**:\n\n- Simplify & speed up lexer (@max-sixty, #3426, #3418)\n\n**New Contributors**:\n\n- @davidot, with #3450\n\n## 0.9.4 — 2023-08-24\n\n0.9.4 is a small release with some improvements and bug fixes in the compiler\nand `prqlc`. And, the documentation and CI are continually being improved.\n\nThis release has 110 commits from 9 contributors. Selected changes:\n\n**Features**:\n\n- Strings can be delimited with any odd number of quote characters. The logic\n  for lexing quotes is now simpler and slightly faster. Escapes in\n  single-quote-delimited strings escape single-quotes rather than double-quotes.\n  (@max-sixty, #3274)\n\n**Fixes**:\n\n- S-strings within double braces now parse correctly (@max-sixty, #3265)\n\n**Documentation**:\n\n- New docs for strings (@max-sixty, #3281)\n\n**Web**:\n\n- Improve syntax highlighting for numbers in the book & website (@max-sixty,\n  #3261)\n- Add ClickHouse integration to docs (@max-sixty, #3251)\n\n**Integrations**:\n\n- `prqlc` no longer displays a prompt when piping a query into its stdin\n  (@max-sixty, #3248).\n- Add a minimal example for use `prql-lib` with Zig (@vanillajonathan, #3372)\n\n**Internal changes**:\n\n- Overhaul our CI to run a cohesive set of tests depending on the specific\n  changes in the PR, and elide all others. This cuts CI latency to less than\n  three minutes for most changes, and enables GitHub's auto-merge to wait for\n  all relevant tests. It also reduces the CI time on merging to main, by moving\n  some tests to only run on specific path changes or on our nightly run.\n\n  We now have one label we can add to PRs to run more tests — `pr-nightly`.\n  (@max-sixty, #3317 & others).\n\n- Auto-merge PRs for backports or pre-commit updates (@max-sixty, #3246)\n- Add a workflow to create an issue when the scheduled nightly workflow fails\n  (@max-sixty, #3304)\n\n**New Contributors**:\n\n- @FinnRG, with #3292\n- @sitiom, with #3353\n\n## 0.9.3 — 2023-08-02\n\n0.9.3 is a small release, with mostly documentation, internal, and CI changes.\n\nThis release has 85 commits from 10 contributors.\n\nWe'd like to welcome @not-my-profile as someone who has helped with lots of\ninternal refactoring in the past couple of weeks.\n\n**New Contributors**:\n\n- @vthriller, with #3171\n- @postmeback, with #3216\n\n## 0.9.2 — 2023-07-25\n\n0.9.2 is a hotfix release to fix an issue in the 0.9.0 & 0.9.1 release\npipelines.\n\n## 0.9.1 — 2023-07-25\n\n0.9.1 is a hotfix release to fix an issue in the 0.9.0 release pipeline.\n\n## 0.9.0 — 2023-07-24\n\n0.9.0 is probably PRQL's biggest ever release. We have dialect-specific\nstandard-libraries, a regex operator, an initial implementation of multiple-file\nprojects & modules, lots of bug fixes, and many many internal changes.\n\nWe've made a few backward incompatible syntax changes. Most queries will work\nwith a simple find/replace; see below for details.\n\nThe release has 421 commits from 12 contributors.\n\nA small selection of the changes:\n\n**Language**:\n\n- The major breaking change is a new syntax for lists, which have been renamed\n  to _tuples_, and are now represented with braces `{}` rather than brackets\n  `[]`.\n\n  To convert previous PRQL queries to this new syntax simply change `[ ... ]` to\n  `{ ... }`.\n\n  We made the syntax change to incorporate arrays. Almost every major language\n  uses `[]` for arrays. We are adopting that convention — arrays use `[]`,\n  tuples will use `{}`. (Though we recognize that `{}` for tuples is also rare\n  (Hi, Erlang!), but didn't want to further load parentheses with meaning.)\n\n  Arrays are conceptually similar to columns — their elements have a single\n  type. Array syntax can't contain assignments.\n\n  As part of this, we've also formalized tuples as containing both individual\n  items (`select {foo, baz}`), and assignments (`select {foo=bar, baz=fuz}`).\n\n- Some significant changes regarding SQL dialects:\n  - Operators and functions can be defined on per-dialect basis. (@aljazerzen,\n    #2681)\n  - _Breaking_: The `sql.duckdb` target supports DuckDB 0.8 (@eitsupi, #2810).\n  - _Breaking_: The `sql.hive` target is removed (@eitsupi, #2837).\n\n- New arithmetic operators. These compile to different function or operator\n  depending on the target.\n  - _Breaking_: Operator `/` now always performs floating division (@aljazerzen,\n    #2684). See the\n    [Division docs](https://prql-lang.org/book/reference/syntax/operators.html#division-and-integer-division)\n    for details.\n\n  - Truncated integer division operator `//` (@aljazerzen, #2684). See the\n    [Division docs](https://prql-lang.org/book/reference/syntax/operators.html#division-and-integer-division)\n    for details.\n\n  - Regex search operator `~=` (@max-sixty, #2458). An example:\n\n    ```prql no-eval\n    from tracks\n    filter (name ~= \"Love\")\n    ```\n\n    ...compiles to;\n\n    ```sql\n    SELECT\n      *\n    FROM\n      tracks\n    WHERE\n      REGEXP(name, 'Love')\n    ```\n\n    ...though the exact form differs by dialect; see the\n    [Regex docs](https://prql-lang.org/book/reference/syntax/operators.html#regex)\n    for more details.\n\n- New aggregation functions: `every`, `any`, `average`, and `concat_array`.\n  _Breaking:_ Remove `avg` in favor of `average`.\n\n- _Breaking:_ We've changed our function declaration syntax to match other\n  declarations. Functions were one of the first language constructs in PRQL, and\n  since then we've added normal declarations there's no compelling reason for\n  functions to be different.\n\n  ```prql no-eval\n  let add = a b -> a + b\n  ```\n\n  Previously, this was:\n\n  ```prql no-eval\n  func add a b -> a + b\n  ```\n\n- Experimental modules, which allow importing declarations from other files.\n  Docs are forthcoming.\n\n- Relation literals create a relation (a \"table\") as an _array_ of _tuples_.\n  This example demonstrates the new syntax for arrays `[]` and tuples `{}`.\n  (@aljazerzen, #2605)\n\n  ```prql no-eval\n  from [{a=5, b=false}, {a=6, b=true}]\n  filter b == true\n  select a\n  ```\n\n- `this` can be used to refer to the current pipeline, for situations where\n  plain column name would be ambiguous:\n\n  ```prql no-eval\n  from x\n  derive sum = my_column\n  select this.sum   # does not conflict with `std.sum`\n  ```\n\n  Within a `join` transform, there is also a reference to the right relation:\n  `that`.\n\n- _Breaking:_ functions `count`, `rank` and `row_number` now require an argument\n  of the array to operate on. In most cases you can directly replace `count`\n  with `count this`. The `non_null` argument of `count` has been removed.\n\n**Features**:\n\n- We've changed how we handle colors.\n\n  `Options::color` is deprecated and has no effect. Code which consumes\n  `prql_compiler::compile` should instead accept the output with colors and use\n  a library such as `anstream` to handle the presentation of colors. To ensure\n  minimal disruption, `prql_compiler` will currently strip color codes when a\n  standard environment variable such as `CLI_COLOR=0` is set or when it detects\n  `stderr` is not a TTY.\n\n  We now use the [`anstream`](https://github.com/rust-cli/anstyle) library in\n  `prqlc` & `prql-compiler`.\n\n  (@max-sixty, #2773)\n\n- `prqlc` can now show backtraces when the standard backtrace env var\n  (`RUST_BACKTRACE`) is active. (@max-sixty, #2751)\n\n**Fixes**:\n\n- Numbers expressed with scientific notation — `1e9` — are now handled correctly\n  by the compiler (@max-sixty, #2865).\n\n**Integrations**:\n\n- prql-python now provides type hints (@philpep, #2912)\n\n**Internal changes**:\n\n- Annotations in PRQL. These have limited support but are currently used to\n  specify binding strengths. They're modeled after Rust's annotations, but with\n  `@` syntax, more similar to traditional decorators. (#2729)\n\n  ```prql no-eval\n  @{binding_strength=11}\n  let mod = l r -> s\"{l} % {r}\"\n  ```\n\n- Remove BigQuery's special handling of quoted identifiers, now that our module\n  system handles its semantics (@max-sixty, #2609).\n\n- ClickHouse is tested in CI (@eitsupi, #2815).\n\n**New Contributors**:\n\n- @maxmcd, with #2533\n- @khoa165, with #2876\n- @philpep, with #2912\n- @not-my-profile, with #2971\n\n## 0.8.1 — 2023-04-29\n\n0.8.1 is a small release with a new `list-targets` command in `prqlc`, some\ndocumentation improvements, and some internal improvements.\n\nThis release has 41 commits from 8 contributors.\n\nFrom the broader perspective of the project, we're increasing the relative\nprioritization of it being easy for folks to actually use PRQL — either with\nexisting tools, or a tool we'd build. We'll be thinking about & discussing the\nbest way to do that over the next few weeks.\n\n## 0.8.0 — 2023-04-14\n\n0.8.0 renames the `and` & `or` operators to `&&` & `||` respectively,\nreorganizes the Syntax section in the book, and introduces `read_parquet` &\n`read_csv` functions for reading files with DuckDB.\n\nThis release has 38 commits from 8 contributors. Selected changes:\n\n**Features**:\n\n- Rename `and` to `&&` and `or` to `||`. Operators which are symbols are now\n  consistently infix, while \"words\" are now consistently functions (@aljazerzen,\n  #2422).\n\n- New functions `read_parquet` and `read_csv`, which mirror the DuckDB\n  functions, instructing the database to read from files (@max-sixty, #2409).\n\n## 0.7.1 — 2023-04-03\n\n0.7.1 is a hotfix release to fix `prql-js`'s `npm install` behavior when being\ninstalled as a dependency.\n\nThis release has 17 commits from 4 contributors.\n\n## 0.7.0 — 2023-04-01\n\n0.7.0 is a fairly small release in terms of new features, with lots of internal\nimprovements, such as integration tests with a whole range of DBs, a blog post\non Pi day, RFCs for a type system, and more robust language bindings.\n\nThere's a very small breaking change to the Rust API, hence the minor version\nbump.\n\nHere's our April 2023 Update, from our\n[Readme](https://github.com/PRQL/prql/blob/main/README.md):\n\n> ### April 2023 update\n>\n> PRQL is being actively developed by a growing community. It's ready to use by\n> the intrepid, either as part of one of our supported extensions, or within\n> your own tools, using one of our supported language bindings.\n>\n> PRQL still has some minor bugs and some missing features, and probably is only\n> ready to be rolled out to non-technical teams for fairly simple queries.\n>\n> Here's our current [Roadmap](https://prql-lang.org/roadmap/) and our\n> [Milestones.](https://github.com/PRQL/prql/milestones)\n>\n> Our immediate focus for the code is on:\n>\n> - Building out the next few big features, including\n>   [types](https://github.com/PRQL/prql/pull/1964) and\n>   [modules](https://github.com/PRQL/prql/pull/2129).\n> - Ensuring our supported features feel extremely robust; resolving any\n>   [priority bugs](https://github.com/PRQL/prql/issues?q=is%3Aissue+is%3Aopen+label%3Abug+label%3Apriority).\n>\n> We're also spending time thinking about:\n>\n> - Making it really easy to start using PRQL. We're doing that by building\n>   integrations with tools that folks already use; for example our VS Code\n>   extension & Jupyter integration. If there are tools you're familiar with\n>   that you think would be open to integrating with PRQL, please let us know in\n>   an issue.\n> - Making it easier to contribute to the compiler. We have a wide group of\n>   contributors to the project, but contributions to the compiler itself are\n>   quite concentrated. We're keen to expand this;\n>   [#1840](https://github.com/PRQL/prql/issues/1840) for feedback.\n\n---\n\nThe release has 131 commits from 10 contributors. Particular credit goes to to\n@eitsupi & @jelenkee, who have made significant contributions, and\n@vanillajonathan, whose prolific contribution include our growing language\nbindings.\n\nA small selection of the changes:\n\n**Features**:\n\n- `prqlc compile` adds `--color` & `--include-signature-comment` options.\n  (@max-sixty, #2267)\n\n**Web**:\n\n- Added the PRQL snippets from the book to the\n  [Playground](https://prql-lang.org/playground/) (@jelenkee, #2197)\n\n**Internal changes**:\n\n- _Breaking_: The `compile` function's `Options` now includes a `color` member,\n  which determines whether error messages use ANSI color codes. This is\n  technically a breaking change to the API. (@max-sixty, #2251)\n- The `Error` struct now exposes the `MessageKind` enum. (@vanillajonathan,\n  #2307)\n- Integration tests run in CI with DuckDB, SQLite, PostgreSQL, MySQL and SQL\n  Server (@jelenkee, #2286)\n\n**New Contributors**:\n\n- @k-nut, with #2294\n\n## 0.6.1 — 2023-03-12\n\n0.6.1 is a small release containing an internal refactoring and improved\nbindings for C, PHP & .NET.\n\nThis release has 54 commits from 6 contributors. Selected changes:\n\n**Fixes**:\n\n- No longer incorrectly compile to `DISTINCT` when a `take 1` refers to a\n  different set of columns than are in the `group`. (@max-sixty, with thanks to\n  @cottrell, #2109)\n- The version specification of the dependency Chumsky was bumped from `0.9.0` to\n  `0.9.2`. `0.9.0` has a bug that causes an infinite loop. (@eitsupi, #2110)\n\n**Documentation**:\n\n- Add a policy for which bindings are supported / unsupported / nascent. See\n  <https://prql-lang.org/book/project/bindings/index.html> for more details\n  (@max-sixty, #2062) (@max-sixty, #2062)\n\n**Integrations**:\n\n- [prql-lib] Added C++ header file. (@vanillajonathan, #2126)\n\n**Internal changes**:\n\n- Many of the items that were in the root of the repo have been aggregated into\n  `web` & `bindings`, simplifying the repo's structure. There's also `grammars`\n  & `packages` (@max-sixty, #2135, #2117, #2121).\n\n## 0.6.0 — 2023-03-08\n\n0.6.0 introduces a rewritten parser, giving us the ability to dramatically\nimprove error messages, renames `switch` to `case` and includes lots of minor\nimprovements and fixes. It also introduces `loop`, which compiles to\n`WITH RECURSIVE`, as a highly experimental feature.\n\nThere are a few cases of breaking changes, including switching `switch` to\n`case`, in case that's confusing. There are also some minor parsing changes\noutlined below.\n\nThis release has 108 commits from 11 contributors. Selected changes:\n\n**Features**:\n\n- Add a (highly experimental) `loop` language feature, which translates to\n  `WITH RECURSIVE`. We expect changes and refinements in upcoming releases.\n  (#1642, @aljazerzen)\n- Rename the experimental `switch` function to `case` given it more closely\n  matches the traditional semantics of `case`. (@max-sixty, #2036)\n- Change the `case` syntax to use `=>` instead of `->` to distinguish it from\n  function syntax.\n- Convert parser from pest to Chumsky (@aljazerzen, #1818)\n  - Improved error messages, and the potential to make even better in the\n    future. Many of these improvements come from error recovery.\n  - String escapes (`\\n \\t`).\n  - Raw strings that don't escape backslashes.\n  - String interpolations can only contain identifiers and not any expression.\n  - Operator associativity has been changed from right-to-left to left-to-right\n    to be more similar to other conventional languages.\n  - `and` now has a higher precedence than `or` (of same reason as the previous\n    point).\n  - Dates, times and timestamps have stricter parsing rules.\n  - `let`, `func`, `prql`, `case` are now treated as keywords.\n  - Float literals without fraction part are not allowed anymore (`1.`).\n- Add a `--format` option to `prqlc parse` which can return the AST in YAML\n  (@max-sixty, #1962)\n- Add a new subcommand `prqlc jinja`. (@aljazerzen, #1722)\n- _Breaking_: prql-compiler no longer passes text containing `{{` & `}}` through\n  to the output. (@aljazerzen, #1722)\n\n  For example, the following PRQL query\n\n  ```prql no-eval\n  from {{foo}}\n  ```\n\n  was compiled to the following SQL previously, but now it raises an error.\n\n  ```sql\n  SELECT\n    *\n  FROM\n    {{ foo }}\n  ```\n\n  This pass-through feature existed for integration with dbt.\n\n  We're again considering how to best integrate with dbt, and this change is\n  based on the idea that the jinja macro should run before the PRQL compiler.\n\n  If you're interested in dbt integration, subscribe or 👍 to\n  <https://github.com/dbt-labs/dbt-core/pull/5982>.\n\n- A new compile target `\"sql.any\"`. When `\"sql.any\"` is used as the target of\n  the compile function's option, the target contained in the query header will\n  be used. (@aljazerzen, #1995)\n- Support for SQL parameters with similar syntax (#1957, @aljazerzen)\n- Allow `:` to be elided in timezones, such as `0800` in\n  `@2020-01-01T13:19:55-0800` (@max-sixty, #1991).\n- Add `std.upper` and `std.lower` functions for changing string casing\n  (@Jelenkee, #2019).\n\n**Fixes**:\n\n- `prqlc compile` returns a non-zero exit code for invalid queries. (@max-sixty,\n  #1924)\n- Identifiers can contain any alphabetic unicode characters (@max-sixty, #2003)\n\n**Documentation**:\n\n- Operator precedence (@aljazerzen, #1818)\n- Error messages for invalid queries are displayed in the book (@max-sixty,\n  #2015)\n\n**Integrations**:\n\n- [prql-php] Added PHP bindings. (@vanillajonathan, #1860)\n- [prql-dotnet] Added .NET bindings. (@vanillajonathan, #1917)\n- [prql-lib] Added C header file. (@vanillajonathan, #1879)\n- Added a workflow building a `.deb` on each release. (Note that it's not yet\n  published on each release). (@vanillajonathan, #1883)\n- Added a workflow building a `.rpm` on each release. (Note that it's not yet\n  published on each release). (@vanillajonathan, #1918)\n- Added a workflow building a Snap package on each release. (@vanillajonathan,\n  #1881)\n\n**Internal changes**:\n\n- Test that the output of our nascent autoformatter can be successfully compiled\n  into SQL. Failing examples are now clearly labeled. (@max-sixty, #2016)\n- Definition files have been added to configure\n  [Dev Containers](https://containers.dev/) for Rust development environment.\n  (@eitsupi, #1893, #2025, #2028)\n\n**New Contributors**:\n\n- @linux-china, with #1971\n- @Jelenkee, with #2019\n\n## 0.5.2 — 2023-02-18\n\n0.5.2 is a tiny release to fix an build issue in yesterday's `prql-js` 0.5.1\nrelease.\n\nThis release has 7 commits from 2 contributors.\n\n**New Contributors**:\n\n- @matthias-Q, with #1873\n\n## 0.5.1 — 2023-02-17\n\n0.5.1 contains a few fixes, and another change to how bindings handle default\ntarget / dialects.\n\nThis release has 53 commits from 7 contributors. Selected changes:\n\n**Fixes**:\n\n- Delegate dividing literal integers to the DB. Previously integer division was\n  executed during PRQL compilation, which could be confusing given that behavior\n  is different across DBs. Other arithmetic operations are still executed during\n  compilation. (@max-sixty, #1747)\n\n**Documentation**:\n\n- Add docs on the `from_text` transform (@max-sixty, #1756)\n\n**Integrations**:\n\n- [prql-js] Default compile target changed from `Sql(Generic)` to `Sql(None)`.\n  (@eitsupi, #1856)\n- [prql-python] Compilation options can now be specified from Python. (@eitsupi,\n  #1807)\n- [prql-python] Default compile target changed from `Sql(Generic)` to\n  `Sql(None)`. (@eitsupi, #1861)\n\n**New Contributors**:\n\n- @vanillajonathan, with #1766\n\n## 0.5.0 — 2023-02-08\n\n0.5.0 contains a few fixes, some improvements to bindings, lots of docs\nimprovements, and some work on forthcoming features. It contains one breaking\nchange in the compiler's `Options` interface.\n\nThis release has 74 commits from 12 contributors. Selected changes:\n\n**Features**:\n\n- Change public API to use target instead of dialect in preparation for feature\n  work (@aljazerzen, #1684)\n\n- `prqlc watch` command which watches filesystem for changes and compiles .prql\n  files to .sql (@aljazerzen, #1708)\n\n**Fixes**:\n\n- Support double brackets in s-strings which aren't symmetric (@max-sixty,\n  #1650)\n- Support Postgres's Interval syntax (@max-sixty, #1649)\n- Fixed tests for `prql-elixir` with macOS (@kasvith, #1707)\n\n**Documentation**:\n\n- Add a documentation test for prql-compiler, update prql-compiler README, and\n  include the README in the prql book section for Rust bindings. The code\n  examples in the README are included and tested as doctests in the\n  prql-compiler (@nkicg6, #1679)\n\n**Internal changes**:\n\n- Add tests for all PRQL website examples to prql-python to ensure compiled\n  results match expected SQL (@nkicg6, #1719)\n\n**New Contributors**:\n\n- @ruslandoga, with #1628\n- @RalfNorthman, with #1632\n- @nicot, with #1662\n\n## 0.4.2 — 2023-01-25\n\n**Features**:\n\n- New `from_text format-arg string-arg` function that supports JSON and CSV\n  formats. _format-arg_ can be `format:csv` or `format:json`. _string-arg_ can\n  be a string in any format. (@aljazerzen & @snth, #1514)\n\n  ```prql no-eval\n  from_text format:csv \"\"\"\n  a,b,c\n  1,2,3\n  4,5,6\n  \"\"\"\n  ```\n\n  ```prql no-eval\n  from_text format:json '''\n      [{\"a\": 1, \"b\": \"x\", \"c\": false }, {\"a\": 4, \"b\": \"y\", \"c\": null }]\n  '''\n  ```\n\n  ```prql no-eval\n  from_text format:json '''{\n      \"columns\": [\"a\", \"b\", \"c\"],\n      \"data\": [\n          [1, \"x\", false],\n          [4, \"y\", null]\n      ]\n  }'''\n  ```\n\n  For now, the argument is limited to string constants.\n\n**Fixes**\n\n- Export constructor for SQLCompileOptions (@bcho, #1621)\n- Remove backticks in count_distinct (@aljazerzen, #1611)\n\n**New Contributors**\n\n- @1Kinoti, with #1596\n- @veenaamb, with #1614\n\n## 0.4.1 — 2023-01-18\n\n0.4.1 comes a few days after 0.4.0, with a couple of features and the release of\n`prqlc`, the CLI crate.\n\n0.4.1 has 35 commits from 6 contributors.\n\n**Features**:\n\n- Inferred column names include the relation name (@aljazerzen, #1550):\n\n  ```prql no-eval\n  from albums\n  select title # name used to be inferred as title only\n  select albums.title # so using albums was not possible here\n  ```\n\n- Quoted identifiers such as `dir/*.parquet` are passed through to SQL.\n  (@max-sixty, #1516).\n\n- The CLI is installed with `cargo install prqlc`. The binary was renamed in\n  0.4.0 but required an additional `--features` flag, which has been removed in\n  favor of this new crate (@max-sixty & @aljazerzen, #1549).\n\n**New Contributors**:\n\n- @fool1280, with #1554\n- @nkicg6, with #1567\n\n## 0.4.0 — 2023-01-15\n\n0.4.0 brings lots of new features including `case`, `select ![]` and numbers\nwith underscores. We have initial (unpublished) bindings to Elixir. And there's\nthe usual improvements to fixes & documentation (only a minority are listed\nbelow in this release).\n\n0.4.0 also has some breaking changes: `table` is `let`, `dialect` is renamed to\n`target`, and the compiler's API has changed. Full details below.\n\n**Features**:\n\n- Defining a temporary table is now expressed as `let` rather than `table`\n  (@aljazerzen, #1315). See the\n  [tables docs](https://prql-lang.org/book/reference/declarations/variables.html)\n  for details.\n\n- _Experimental:_ The\n  [`case`](https://prql-lang.org/book/reference/syntax/case.html) function sets\n  a variable to a value based on one of several expressions (@aljazerzen,\n  #1278).\n\n  ```prql no-eval\n  derive var = case [\n    score <= 10 -> \"low\",\n    score <= 30 -> \"medium\",\n    score <= 70 -> \"high\",\n    true -> \"very high\",\n  ]\n  ```\n\n  ...compiles to:\n\n  ```sql\n  SELECT\n    *,\n    CASE\n      WHEN score <= 10 THEN 'low'\n      WHEN score <= 30 THEN 'medium'\n      WHEN score <= 70 THEN 'high'\n      ELSE 'very high'\n    END AS var\n  FROM\n    bar\n  ```\n\n  Check out the\n  [`case` docs](https://prql-lang.org/book/reference/syntax/case.html) for more\n  details.\n\n- _Experimental:_ Columns can be excluded by name with `select` (@aljazerzen,\n  #1329)\n\n  ```prql no-eval\n  from albums\n  select ![title, composer]\n  ```\n\n- _Experimental:_ `append` transform, equivalent to `UNION ALL` in SQL.\n  (@aljazerzen, #894)\n\n  ```prql no-eval\n  from employees\n  append managers\n  ```\n\n  Check out the\n  [`append` docs](https://prql-lang.org/book/reference/stdlib/transforms/append.html)\n  for more details.\n\n- Numbers can contain underscores, which can make reading long numbers easier\n  (@max-sixty, #1467):\n\n  ```prql no-eval\n  from numbers\n  select {\n      small = 1.000_000_1,\n      big = 5_000_000,\n  }\n  ```\n\n- The SQL output contains a comment with the PRQL compiler version (@aljazerzen,\n  #1322)\n- `dialect` is renamed to `target`, and its values are prefixed with `sql.`\n  (@max-sixty, #1388); for example:\n\n  ```prql no-eval\n  prql target:sql.bigquery  # previously was `dialect:bigquery`\n\n  from employees\n  ```\n\n  This gives us the flexibility to target other languages than SQL in the long\n  term.\n\n- Tables definitions can contain a bare s-string (@max-sixty, #1422), which\n  enables us to include a full CTE of SQL, for example:\n\n  ```prql no-eval\n  let grouping = s\"\"\"\n    SELECT SUM(a)\n    FROM tbl\n    GROUP BY\n      GROUPING SETS\n      ((b, c, d), (d), (b, d))\n  \"\"\"\n  ```\n\n- Ranges supplied to `in` can be half-open (@aljazerzen, #1330).\n\n- The crate's external API has changed to allow for compiling to intermediate\n  representation. This also affects bindings. See\n  [`prql-compiler` docs](https://docs.rs/prql-compiler/latest/prql_compiler/)\n  for more details.\n\n**Fixes**:\n\n[This release, the changelog only contains a subset of fixes]\n\n- Allow interpolations in table s-strings (@aljazerzen, #1337)\n\n**Documentation**:\n\n[This release, the changelog only contains a subset of documentation\nimprovements]\n\n- Add docs on aliases in\n  [Select](https://prql-lang.org/book/reference/stdlib/transforms/select.html)\n- Add JS template literal and multiline example (@BCsabaEngine, #1432)\n- JS template literal and multiline example (@BCsabaEngine, #1432)\n- Improve prql-compiler docs & examples (@aljazerzen, #1515)\n- Fix string highlighting in book (@max-sixty, #1264)\n\n**Web**:\n\n- The playground allows querying some sample data. As before, the result updates\n  on every keystroke. (@aljazerzen, #1305)\n\n**Integrations**:\n\n[This release, the changelog only contains a subset of integration improvements]\n\n- Added Elixir integration exposing PRQL functions as NIFs (#1500, @kasvith)\n- Exposed Elixir flavor with exceptions (#1513, @kasvith)\n- Rename `prql-compiler` binary to `prqlc` (@aljazerzen #1515)\n\n**Internal changes**:\n\n[This release, the changelog only contains a subset of internal changes]\n\n- Add parsing for negative select (@max-sixty, #1317)\n- Allow for additional builtin functions (@aljazerzen, #1325)\n- Add an automated check for typos (@max-sixty, #1421)\n- Add tasks for running playground & book (@max-sixty, #1265)\n- Add tasks for running tests on every file change (@max-sixty, #1380)\n\n**New contributors**:\n\n- @EArazli, with #1359\n- @boramalper, with #1362\n- @allurefx, with #1377\n- @bcho, with #1375\n- @JettChenT, with #1385\n- @BlurrechDev, with #1411\n- @BCsabaEngine, with #1432\n- @kasvith, with #1500\n\n## 0.3.1 - 2022-12-03\n\n0.3.1 brings a couple of small improvements and fixes.\n\n**Features**:\n\n- Support for using s-strings for `from` (#1197, @aljazerzen)\n\n  ```prql no-eval\n  from s\"SELECT * FROM employees WHERE foo > 5\"\n  ```\n\n- Helpful error message when referencing a table in an s-string (#1203,\n  @aljazerzen)\n\n**Fixes**:\n\n- Multiple columns with same name created (#1211, @aljazerzen)\n- Renaming via select breaks preceding sorting (#1204, @aljazerzen)\n- Same column gets selected multiple times (#1186, @mklopets)\n\n**Internal**:\n\n- Update Github Actions and Workflows to current version numbers (and avoid\n  using Node 12)\n\n## 0.3.0 — 2022-11-29\n\n🎉 0.3.0 is the biggest ever change in PRQL's compiler, rewriting much of the\ninternals: the compiler now has a semantic understanding of expressions,\nincluding resolving names & building a DAG of column lineage 🎉.\n\nWhile the immediate changes to the language are modest — some long-running bugs\nare fixed — this unlocks the development of many of the project's long-term\npriorities, such as type-checking & auto-complete. And it simplifies the\nbuilding of our next language features, such as match-case expressions, unions &\ntable expressions.\n\n@aljazerzen has (mostly single-handedly) done this work over the past few\nmonths. The project owes him immense appreciation.\n\n**Breaking changes**:\n\nWe've had to make some modest breaking changes for 0.3:\n\n- _Pipelines must start with `from`_. For example, a pipeline with only\n  `derive foo = 5`, with no `from` transform, is no longer valid. Depending on\n  demand for this feature, it would be possible to add this back.\n\n- _Shared column names now require `==` in a join_. The existing approach is\n  ambiguous to the compiler — `id` in the following example could be a boolean\n  column.\n\n  ```diff\n  from employees\n  -join positions [id]\n  +join positions [==id]\n  ```\n\n- _Table references containing periods must be surrounded by backticks_. For\n  example, when referencing a schema name:\n\n  ```diff\n  -from public.sometable\n  +from `public.sometable`\n  ```\n\n**Features**:\n\n- Change self equality op to `==` (#1176, @aljazerzen)\n- Add logging (@aljazerzen)\n- Add clickhouse dialect (#1090, @max-sixty)\n- Allow namespaces & tables to contain `.` (#1079, @aljazerzen)\n\n**Fixes**:\n\n- Deduplicate column appearing in `SELECT` multiple times (#1186, @aljazerzen)\n- Fix uppercase table names (#1184, @aljazerzen)\n- Omit table name when only one ident in SELECT (#1094, @aljazerzen)\n\n**Documentation**:\n\n- Add chapter on semantics' internals (@aljazerzen, #1028)\n- Add note about nesting variables in s-strings (@max-sixty, #1163)\n\n**Internal changes**:\n\n- Flatten group and window (#1120, @aljazerzen)\n- Split ast into expr and stmt (@aljazerzen)\n- Refactor associativity (#1156, @aljazerzen)\n- Rename Ident constructor to `from_name` (#1084, @aljazerzen)\n- Refactor rq folding (#1177, @aljazerzen)\n- Add tests for reported bugs fixes in semantic (#1174, @aljazerzen)\n- Bump duckdb from 0.5.0 to 0.6.0 (#1132)\n- Bump once_cell from 1.15.0 to 1.16.0 (#1101)\n- Bump pest from 2.4.0 to 2.5.0 (#1161)\n- Bump pest_derive from 2.4.0 to 2.5.0 (#1179)\n- Bump sqlparser from 0.25.0 to 0.27.0 (#1131)\n- Bump trash from 2.1.5 to 3.0.0 (#1178)\n\n## 0.2.11 — 2022-11-20\n\n0.2.11 contains a few helpful fixes.\n\nWork continues on our `semantic` refactor — look out for 0.3.0 soon! Many thanks\nto @aljazerzen for his continued contributions to this.\n\nNote: 0.2.10 was skipped due to this maintainer's inability to read his own docs\non bumping versions...\n\n**Features**:\n\n- Detect when compiler version is behind query version (@MarinPostma, #1058)\n- Add `__version__` to prql-python package (@max-sixty, #1034)\n\n**Fixes**:\n\n- Fix nesting of expressions with equal binding strength and left associativity,\n  such as `a - (b - c)` (@max-sixty, #1136)\n- Retain floats without significant digits as floats (@max-sixty, #1141)\n\n**Documentation**:\n\n- Add documentation of `prqlr` bindings (@eitsupi, #1091)\n- Add a 'Why PRQL' section to the website (@max-sixty, #1098)\n- Add @snth to core-devs (@max-sixty, #1050)\n\n**Internal changes**:\n\n- Use workspace versioning (@max-sixty, #1065)\n\n## 0.2.9 — 2022-10-14\n\n0.2.9 is a small release containing a bug fix for empty strings.\n\n**Fixes**:\n\n- Fix parsing of empty strings (@aljazerzen, #1024)\n\n## 0.2.8 — 2022-10-10\n\n0.2.8 is another modest release with some fixes, doc improvements, bindings\nimprovements, and lots of internal changes. Note that one of the fixes causes\nthe behavior of `round` and `cast` to change slightly — though it's handled as a\nfix rather than a breaking change in semantic versioning.\n\n**Fixes**:\n\n- Change order of the `round` & `cast` function parameters to have the column\n  last; for example `round 2 foo_col` / `cast int foo`. This is consistent with\n  other functions, and makes piping possible:\n\n  ```prql no-eval\n  derive [\n    gross_salary = (salary + payroll_tax | as int),\n    gross_salary_rounded = (gross_salary | round 0),\n  ]\n  ```\n\n**Documentation**:\n\n- Split `DEVELOPMENT.md` from `CONTRIBUTING.md` (@richb-hanover, #1010)\n- Make s-strings more prominent in website intro (@max-sixty, #982)\n\n**Web**:\n\n- Add GitHub star count to website (@max-sixty, #990)\n\n**Integrations**:\n\n- Expose a shortened error message, in particular for the VS Code extension\n  (@aljazerzen, #1005)\n\n**Internal changes**:\n\n- Specify 1.60.0 as minimum Rust version (@max-sixty, #1011)\n- Remove old `wee-alloc` code (@max-sixty, #1013)\n- Upgrade clap to version 4 (@aj-bagwell, #1004)\n- Improve book-building script in Taskfile (@max-sixty, #989)\n- Publish website using an artifact rather than a long-lived branch (@max-sixty,\n  #1009)\n\n## 0.2.7 — 2022-09-17\n\n0.2.7 is a fairly modest release, six weeks after 0.2.6. We have some more\nsignificant features, including a `union` operator and an overhaul of our type\nsystem, as open PRs which will follow in future releases.\n\nWe also have new features in the\n[VS Code extension](https://github.com/PRQL/prql-code), courtesy of\n@jiripospisil, including a live output panel.\n\n**Fixes**:\n\n- `range_of_ranges` checks the Range end is smaller than its start (@shuozeli,\n  #946)\n\n**Documentation**:\n\n- Improve various docs (@max-sixty, #974, #971, #972, #970, #925)\n- Add reference to EdgeDB's blog post in our FAQ (@max-sixty, #922)\n- Fix typos (@kianmeng, #943)\n\n**Integrations**:\n\n- Add `prql-lib`, enabling language bindings with `go` (@sigxcpu76, #923)\n- Fix line numbers in JS exceptions (@charlie-sanders, #929)\n\n**Internal changes**:\n\n- Lock the version of the rust-toolchain, with auto-updates (@max-sixty, #926,\n  #927)\n\n## 0.2.6 — 2022-08-05\n\n**Fixes**:\n\n- Adjust `fmt` to only escape names when needed (@aljazerzen, #907)\n- Fix quoting on upper case `table` names (@max-sixty, #893)\n- Fix scoping of identical column names from multiple tables (@max-sixty, #908)\n- Fix parse error on newlines in a `table` (@sebastiantoh 🆕, #902)\n- Fix quoting of upper case table names (@max-sixty, #893)\n\n**Documentation**:\n\n- Add docs on Architecture (@aljazerzen, #904)\n- Add Changelog (@max-sixty, #890 #891)\n\n**Internal changes**:\n\n- Start trial using Conventional Commits (@max-sixty, #889)\n- Add crates.io release workflow, docs (@max-sixty, #887)\n\n## 0.2.5 - 2022-07-29\n\n0.2.5 is a very small release following 0.2.4 yesterday. It includes:\n\n- Add the ability to represent single brackets in an s-string, with two brackets\n  (#752, @max-sixty)\n- Fix the \"Copy to Clipboard\" command in the Playground, for Firefox (#880,\n  @mklopets)\n\n## 0.2.4 - 2022-07-28\n\n0.2.4 is a small release following 0.2.3 a few days ago. The 0.2.4 release\nincludes:\n\n- Enrich our CLI, adding commands to get different stages of the compilation\n  process (@aljazerzen , #863)\n- Fix multiple `take n` statements in a query, leading to duplicate proxy\n  columns in generated SQL (@charlie-sanders)\n- Fix BigQuery quoting of identifiers in `SELECT` statements (@max-sixty)\n- Some internal changes — reorganize top-level functions (@aljazerzen), add a\n  workflow to track our Rust compilation time (@max-sixty), simplify our simple\n  prql-to-sql tests (@max-sixty)\n\nThanks to @ankane, `prql-compiler` is now available from homebrew core;\n`brew install prql-compiler`[^1].\n\n[^1]:\n    we still need to update docs and add a release workflow for this:\n    <https://github.com/PRQL/prql/issues/866>\n\n## 0.2.3 - 2022-07-24\n\nA couple of weeks since the 0.2.2 release: we've squashed a few bugs, added some\nmid-sized features to the language, and made a bunch of internal improvements.\n\nThe 0.2.3 release includes:\n\n- Allow for escaping otherwise-invalid identifiers (@aljazerzen & @max-sixty)\n- Fix a bug around operator precedence (@max-sixty)\n- Add a section the book on the language bindings (@charlie-sanders)\n- Add tests for our `Display` representation while fixing some existing bugs.\n  This is gradually becoming our code formatter (@arrizalamin)\n- Add a \"copy to clipboard\" button in the Playground (@mklopets)\n- Add lots of guidance to our `CONTRIBUTING.md` around our tests and process for\n  merging (@max-sixty)\n- Add a `prql!` macro for parsing a prql query at compile time (@aljazerzen)\n- Add tests for `prql-js` (@charlie-sanders)\n- Add a `from_json` method for transforming json to a PRQL string (@arrizalamin)\n- Add a workflow to release `prql-java` to Maven (@doki23)\n- Enable running all tests from a PR by adding a `pr-run-all-tests` label\n  (@max-sixty)\n- Have `cargo-release` to bump all crate & npm versions (@max-sixty)\n- Update `prql-js` to use the bundler build of `prql-js` (@mklopets)\n\nAs well as those contribution changes, thanks to those who've reported issues,\nsuch as @mklopets @huw @mm444 @ajfriend.\n\nFrom here, we're planning to continue squashing bugs (albeit more minor than\nthose in this release), adding some features like `union`, while working on\nbigger issues such as type-inference.\n\nWe're also going to document and modularize the compiler further. It's important\nthat we give more people an opportunity to contribute to the guts of PRQL,\nespecially given the number and enthusiasm of contributions to project in\ngeneral — and it's not that easy to do so at the moment. While this is ongoing\nif anyone has something they'd like to work on in the more difficult parts of\nthe compiler, let us know on GitHub or Discord, and we'd be happy to work\ntogether on it.\n\nThank you!\n\n## 0.2.2 - 2022-07-10\n\nWe're a couple of weeks since our 0.2.0 release. Thanks for the surge in\ninterest and contributions! 0.2.2 has some fixes & some internal improvements:\n\n- We now test against SQLite & DuckDB on every commit, to ensure we're producing\n  correct SQL. (@aljazerzen)\n- We have the beginning of Java bindings! (@doki23)\n- Idents surrounded by backticks are passed through to SQL (@max-sixty)\n- More examples on homepage; e.g. `join` & `window`, lots of small docs\n  improvements\n- Automated releases to homebrew (@roG0d)\n- [prql-js](https://github.com/PRQL/prql/tree/main/prqlc/bindings/js) is now a\n  single package for Node, browsers & webpack (@charlie-sanders)\n- Parsing has some fixes, including `>=` and leading underscores in idents\n  (@mklopets)\n- Ranges receive correct syntax highlighting (@max-sixty)\n\nThanks to Aljaž Mur Eržen @aljazerzen , George Roldugin @roldugin , Jasper\nMcCulloch @Jaspooky , Jie Han @doki23 , Marko Klopets @mklopets , Maximilian\nRoos @max-sixty , Rodrigo Garcia @roG0d , Ryan Russell @ryanrussell , Steven\nMaude @StevenMaude , Charlie Sanders @charlie-sanders .\n\nWe're planning to continue collecting bugs & feature requests from users, as\nwell as working on some of the bigger features, like type-inference.\n\nFor those interesting in joining, we also have a new\n[Contributing page](https://github.com/PRQL/prql/blob/main/.github/CONTRIBUTING.md).\n\n## 0.2.0 - 2022-06-27\n\n🎉 🎉 **After several months of building, PRQL is ready to use!** 🎉 🎉\n\n---\n\nHow we got here:\n\nAt the end of January, we published a proposal of a better language for data\ntransformation: PRQL. The reception was better than I could have hoped for — we\nwere no. 2 on HackerNews for a day, and gained 2.5K GitHub stars over the next\nfew days.\n\nBut man cannot live on GitHub Stars alone — we had to do the work to build it.\nSo over the next several months, during many evenings & weekends, a growing\ngroup of us gradually built the compiler, evolved the language, and wrote some\nintegrations.\n\nWe want to double-down on the community and its roots in open source — it's\nincredible that a few of us from all over the globe have collaborated on a\nproject without ever having met. We decided early-on that PRQL would always be\nopen-source and would never have a commercial product (despite lots of outside\ninterest to fund a seed round!). Because languages are so deep in the stack, and\nthe data stack has so many players, the best chance of building a great language\nis to build an open language.\n\n---\n\nWe still have a long way to go. While PRQL is usable, it has lots of missing\nfeatures, and an incredible amount of unfulfilled potential, including a\nlanguage server, cohesion with databases, and type inference. Over the coming\nweeks, we'd like to grow the number of intrepid users experimenting PRQL in\ntheir projects, prioritize features that will unblock them, and then start\nfulfilling PRQL's potential by working through our\n[roadmap](https://prql-lang.org/roadmap/).\n\nThe best way to experience PRQL is to try it. Check out our\n[website](https://prql-lang.org) and the\n[Playground](https://prql-lang.org/playground). Start using PRQL for your own\nprojects in [dbt](https://github.com/prql/dbt-prql),\n[Jupyter notebooks](https://pyprql.readthedocs.io/en/latest/magic_readme.html)\nand Prefect workflows.\n\nKeep in touch with PRQL by following the project on\n[Twitter](https://twitter.com/prql_lang), joining us on\n[Discord](https://discord.gg/eQcfaCmsNc), starring the\n[repo](https://github.com/PRQL/prql).\n\n[Contribute](https://github.com/PRQL/prql/blob/main/.github/CONTRIBUTING.md) to\nthe project — we're a really friendly community, whether you're a recent SQL\nuser or an advanced Rust programmer. We need bug reports, documentation tweaks &\nfeature requests — just as much as we need compiler improvements written in\nRust.\n\n---\n\nI especially want to give [Aljaž Mur Eržen](https://github.com/aljazerzen)\n(@aljazerzen) the credit he deserves, who has contributed the majority of the\ndifficult work of building out the compiler. Much credit also goes to\n@charlie-sanders, one of PRQL's earliest supporters and the author of pyprql,\nand [Ryan Patterson-Cross](https://github.com/rbpatt2019) (@rbpatt2019), who\nbuilt the Jupyter integration among other Python contributions.\n\nOther contributors who deserve a special mention include: @roG0d, @snth,\n@kwigley\n\n---\n\nThank you, and we look forward to your feedback!\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# Claude\n\n## Development Workflow\n\nUse a tiered testing approach—iterate quickly, validate thoroughly:\n\n**Inner loop** (during development, ~5s):\n\n```sh\n# Fast tests on core packages\ntask prqlc:test\n\n# Filtered by test name\ncargo insta test -p prqlc --lib -- resolver\ncargo insta test -p prqlc --test integration -- date\n```\n\n**Before returning to user** (~30s):\n\n```sh\n# Comprehensive prqlc tests - sufficient for most changes\ntask prqlc:pull-request\n```\n\n**Cross-binding changes only** (~2min):\n\n```sh\n# Only when changes affect JS/Python/wasm bindings\ntask test-all\n```\n\nThe test suite is configured to minimize token usage:\n\n- **Nextest** only shows failures and slow tests (not 600 PASS lines)\n- **Cargo builds** use `--quiet` flag (no compilation spam)\n- **Result**: ~52% reduction in output (1128 → 540 lines, ~4.5k tokens)\n\n## Tests\n\nPrefer inline snapshots for almost all tests:\n\n```rust\ninsta::assert_snapshot!(result, @\"expected output\");\n```\n\nInitialize tests with empty snapshots, then run with `--accept`:\n\n```rust\ninsta::assert_snapshot!(result, @\"\");\n```\n\nThe test commands above with `--accept` will fill in the result automatically.\n\n### Test Strategy\n\n**Prefer small inline `insta` snapshot tests** over full integration tests:\n\n- **Use inline tests** for most bug fixes and small features\n  - Add `#[test]` functions in a `#[cfg(test)]` module at the end of the file\n  - Use `insta::assert_snapshot!` for compact, readable test assertions\n  - Fast to run, easy to review in PRs\n\n- **Use integration tests** (`prqlc/tests/integration/queries/*.prql`) only\n  when:\n  - Developing large, complex features that need comprehensive testing\n  - Testing end-to-end behavior across multiple compilation stages\n  - The test requires external resources or multi-file scenarios\n\nExample of a good inline test:\n\n```rust\n#[cfg(test)]\nmod test {\n    use insta::assert_snapshot;\n\n    #[test]\n    fn test_my_feature() {\n        let query = \"from employees | filter country == 'USA'\";\n        assert_snapshot!(crate::tests::compile(query).unwrap(), @\"\");\n    }\n}\n```\n\n## Running the CLI\n\nFor viewing `prqlc` output, for any stage of the compilation process:\n\n```sh\n# Compile PRQL to SQL\ncargo run -p prqlc -- compile \"from employees | filter country == 'USA'\"\n\n# Format PRQL code\ncargo run -p prqlc -- fmt \"from employees | filter country == 'USA'\"\n\n# See all available commands\ncargo run -p prqlc -- --help\n```\n\n## Linting\n\nRun all lints with\n\n```sh\ntask lint\n```\n\n## Error Handling\n\nNever panic on user input or recoverable errors. Use proper error returns:\n\n- ❌ `.unwrap()` on operations that can fail with user input\n- ✅ `?` operator or `return Err(Error::new_simple(\"message\"))`\n- ✅ `.expect(\"reason\")` or `unreachable!()` only for compiler-bug invariants\n\n## Error Messages\n\nError messages should avoid 2nd person (you/your). Use softer modal verbs like\n\"might\" for a friendlier tone:\n\n- ❌ \"are you missing `from` statement?\" → ✅ \"`from` statement might be\n  missing?\"\n- ❌ \"did you forget to specify the column name?\" → ✅ \"column name might be\n  missing?\"\n- ❌ \"you can only use X\" → ✅ \"X requires Y\" (for hard constraints)\n- ❌ \"Have you forgotten an argument?\" → ✅ \"Argument might be missing?\"\n\n## Documentation\n\nFor Claude to view crate documentation:\n\n```sh\n# Build documentation for a specific crate\ncargo doc -p prqlc\n\n# View the generated HTML documentation with the View tool\n# The docs are generated at target/doc/{crate_name}/index.html\nView target/doc/prqlc/index.html\n\n# For specific module documentation\nView target/doc/prqlc/module_name/index.html\n\n# For function documentation\nView target/doc/prqlc/fn.compile.html\n```\n\n## Releases & Environment\n\nFor releases or environment issues, see\n`web/book/src/project/contributing/development.md`.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n  \"prqlc/bindings/elixir/native/prql\",\n  \"prqlc/bindings/java\",\n  \"prqlc/bindings/js\",\n  \"prqlc/bindings/prqlc-c\",\n  \"prqlc/bindings/prqlc-python\",\n  \"prqlc/prqlc-macros\",\n  \"prqlc/prqlc-parser\",\n  \"prqlc/prqlc\",\n  \"prqlc/prqlc/examples/compile-files\", # An example\n  \"web/book\",\n]\nresolver = \"2\"\n\n[workspace.package]\nauthors = [\"PRQL Developers\"]\nedition = \"2021\"\nlicense = \"Apache-2.0\"\nrepository = \"https://github.com/PRQL/prql\"\n# This isn't tested since `cargo-msrv` doesn't support workspaces; instead we\n# test `metadata.msrv` in `prqlc`\nrust-version = \"1.75.0\"\nversion = \"0.13.12\"\n\n[profile.release]\nlto = true\n# Optimize for binary size in releases of all crates,\n# since compiler is fast enough as it is (for now).\nopt-level = \"s\"\n\n[profile.release.package.prqlc-c]\n# Remove some debug symbols (linker needs some of them)\nstrip = \"debuginfo\"\n\n# Insta runs faster this way, ref https://insta.rs/docs/quickstart/\n[profile.dev.package.insta]\nopt-level = 3\n[profile.dev.package.similar]\nopt-level = 3\n\n[workspace.metadata.release]\nallow-branch = [\"*\"]\nconsolidate-commits = true\n\n[workspace.dependencies]\nanyhow = \"1.0.102\"\narrow = { version = \"53\", features = [\n  \"pyarrow\",\n  \"prettyprint\",\n], default-features = false }\nenum-as-inner = \"0.7.0\"\ninsta = { version = \"1.46.3\", features = [\"colors\", \"glob\", \"yaml\", \"filters\"] }\ninsta-cmd = \"0.6.0\"\nitertools = \"0.14.0\"\nlog = \"0.4.29\"\npyo3 = { version = \"0.27.1\", features = [\"abi3-py37\", \"anyhow\"] }\npyo3-build-config = \"0.27.1\"\nschemars = \"1.2.1\"\nsemver = { version = \"1.0.27\", features = [\"serde\"] }\nserde = { version = \"1.0.228\", features = [\"derive\"] }\nserde_json = \"1.0.149\"\nserde_yaml = { version = \"0.9.34\" }\nsimilar = \"2.7.0\"\nsimilar-asserts = \"1.7.0\"\nstrum = { version = \"0.28.0\", features = [\"std\", \"derive\"] }\nstrum_macros = \"0.28.0\"\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# PRQL\n\n<!-- User badges on first line (language docs & chat) -->\n\n<!-- Discord online count is blocked rate limits, can try swapping back [![Discord](https://img.shields.io/discord/936728116712316989?label=discord%20chat&style=for-the-badge)](https://discord.gg/eQcfaCmsNc) -->\n<!-- Twitter followers doesn't work, but leaving the badge there -->\n\n[![Website](https://img.shields.io/badge/INTRO-WEB-blue?style=for-the-badge)](https://prql-lang.org)\n[![Playground](https://img.shields.io/badge/INTRO-PLAYGROUND-blue?style=for-the-badge)](https://prql-lang.org/playground)\n[![Language Docs](https://img.shields.io/badge/DOCS-BOOK-blue?style=for-the-badge)](https://prql-lang.org/book)\n[![Discord](https://img.shields.io/badge/DISCORD-CHAT-indigo?style=for-the-badge&logo=discord)](https://discord.gg/eQcfaCmsNc)\n[![Twitter](https://img.shields.io/twitter/follow/prql_lang?color=%231DA1F2&style=for-the-badge&logo=x)](https://twitter.com/prql_lang)\n\n<!-- Dev badges on second line -->\n\n[![GitHub CI Status](https://img.shields.io/github/actions/workflow/status/prql/prql/tests.yaml?event=push&branch=main&logo=github&style=for-the-badge)](https://github.com/PRQL/prql/actions?query=branch%3Amain+workflow%3Atests)\n[![GitHub contributors](https://img.shields.io/github/contributors/PRQL/prql?style=for-the-badge&logo=github)](https://github.com/PRQL/prql/graphs/contributors)\n[![Stars](https://img.shields.io/github/stars/PRQL/prql?style=for-the-badge&logo=github)](https://github.com/PRQL/prql/stargazers)\n\n**P**ipelined **R**elational **Q**uery **L**anguage, pronounced \"Prequel\".\n\nPRQL is a modern language for transforming data — a simple, powerful, pipelined\nSQL replacement. Like SQL, it's readable, explicit and declarative. Unlike SQL,\nit forms a logical pipeline of transformations, and supports abstractions such\nas variables and functions. It can be used with any database that uses SQL,\nsince it compiles to SQL.\n\nPRQL can be as simple as:\n\n```elm\nfrom tracks\nfilter artist == \"Bob Marley\"                 # Each line transforms the previous result\naggregate {                                   # `aggregate` reduces each column to a value\n  plays    = sum plays,\n  longest  = max length,\n  shortest = min length,                      # Trailing commas are allowed\n}\n```\n\nHere's a larger example of the language:\n\n```elm\nfrom employees\nfilter start_date > @2021-01-01               # Clear date syntax\nderive {                                      # `derive` adds columns / variables\n  gross_salary = salary + (tax ?? 0),         # Terse coalesce\n  gross_cost = gross_salary + benefits_cost,  # Variables can use other variables\n}\nfilter gross_cost > 0\ngroup {title, country} (                      # `group` runs a pipeline over each group\n  aggregate {                                 # `aggregate` reduces each group to a value\n    average gross_salary,\n    sum_gross_cost = sum gross_cost,          # `=` sets a column name\n  }\n)\nfilter sum_gross_cost > 100_000               # `filter` replaces both of SQL's `WHERE` & `HAVING`\nderive id = f\"{title}_{country}\"              # F-strings like Python\nderive country_code = s\"LEFT(country, 2)\"     # S-strings allow using SQL as an escape hatch\nsort {sum_gross_cost, -country}               # `-country` means descending order\ntake 1..20                                    # Range expressions (also valid here as `take 20`)\n```\n\nFor more on the language, more examples & comparisons with SQL, visit\n[prql-lang.org][prql website]. To experiment with PRQL in the browser, check out\n[PRQL Playground][prql playground].\n\n## Current Status - December 2025\n\nPRQL is ready to use by the intrepid, either with our supported integrations, or\nwithin your own tools, using one of our supported language bindings.\n\nPRQL still has some bugs and some missing features, and is probably only ready\nto be rolled out to non-technical teams for fairly simple queries.\n\nDevelopment has slowed in the past few months as we decide how to work on a new\nresolver, which will let us squash many bugs and simplify our code a lot. It'll\nalso let us scale the language without scaling the complexity of the compiler.\n\nWhile we figure that out, we're also thinking about:\n\n- Ensuring our supported features feel extremely robust; resolving any\n  [priority bugs](https://github.com/PRQL/prql/issues?q=is%3Aissue+is%3Aopen+label%3Abug+label%3Apriority).\n  As more folks have started using PRQL, we've had more bug reports — good news,\n  but also gives us more to work on.\n- Filling remaining feature gaps, so that PRQL is possible to use for almost all\n  standard SQL queries.\n- Expanding our set of supported features — we are working to add experimental\n  support for modules / multi-file projects, and for auto-formatting.\n\nAnd:\n\n- Making it really easy to start using PRQL. We're doing that by building\n  integrations with tools that folks already use; for example a VS Code\n  extension, Jupyter integration, and the recent\n  [QStudio](https://www.timestored.com/qstudio/prql-ide) integration. If there\n  are tools you're familiar with that you think would be open to integrating\n  with PRQL, please let us know in an issue.\n- Whether all our initial decisions were correct — for example\n  [how we handle window functions outside of a `window` transform](https://github.com/PRQL/prql/issues/2723).\n- Making it easier to contribute to the compiler. We have a wide group of\n  contributors to the project, but contributions to the compiler itself are\n  quite concentrated. We're keen to expand this;\n  [#1840](https://github.com/PRQL/prql/issues/1840) for feedback, some\n  suggestions on starter issues are below.\n\nWe're increasingly open to contributions for bigger rewrites of the resolver\ngiven how bottlenecked we are on it. If you're interested in contributing,\nplease reach out in an issue or on Discord.\n\n## Get involved\n\nTo stay in touch with PRQL:\n\n- Follow us on [Twitter](https://twitter.com/prql_lang)\n- Join us on [Discord](https://discord.gg/eQcfaCmsNc)\n- Star this repo\n- [Contribute][contributing] — join us in building PRQL, through writing code\n  [(send us your use-cases!)](https://github.com/PRQL/prql/discussions), or\n  inspiring others to use it.\n- See the [development][development] documentation for PRQL. It's easy to get\n  started — the project can be built in a couple of commands, and we're a really\n  friendly community!\n- For those who might be interested in contributing to the code now, check out\n  issues with the\n  [good first issue](https://github.com/PRQL/prql/labels/good%20first%20issue)\n  label. Always feel free to ask questions or open a draft PR.\n\n## Explore\n\n- [PRQL Playground][prql playground] — experiment with PRQL in the browser.\n- [PRQL Book][prql book] — the language documentation.\n- [Jupyter magic](https://pyprql.readthedocs.io/en/latest/magic_readme.html) —\n  run PRQL in Jupyter, either against a DB, or a Pandas DataFrame / CSV /\n  Parquet file through DuckDB.\n- [pyprql Docs](https://pyprql.readthedocs.io) — the pyprql documentation, the\n  Python bindings to PRQL, including Jupyter magic.\n- [PRQL VS Code extension](https://marketplace.visualstudio.com/items?itemName=prql-lang.prql-vscode)\n- [prqlc-js](https://www.npmjs.com/package/prqlc) — JavaScript bindings for\n  PRQL.\n\n## Repo organization\n\nThis repo is composed of:\n\n- **[prqlc](./prqlc/)** — the compiler, written in rust, whose main role is to\n  compile PRQL into SQL. Also contains the CLI and bindings from various\n  languages.\n- **[web](./web/)** — our web content: the [Book][prql book],\n  [Website][prql website], and [Playground][prql playground].\n\nIt also contains our testing / CI infrastructure and development tools. Check\nout our [development docs][development] for more details.\n\n## Contributors\n\nMany thanks to those who've made our progress possible:\n\n[![Contributors](https://contrib.rocks/image?repo=PRQL/prql)](https://github.com/PRQL/prql/graphs/contributors)\n\n[prql book]: https://prql-lang.org/book\n[prql website]: https://prql-lang.org\n[contributing]: https://prql-lang.org/book/project/contributing/\n[development]: https://prql-lang.org/book/project/contributing/development.html\n[prql playground]: https://prql-lang.org/playground\n"
  },
  {
    "path": "Taskfile.yaml",
    "content": "# yaml-language-server: $schema=https://json.schemastore.org/taskfile.json\n\n# Root Taskfile - handles setup, orchestration, and repo-wide operations.\n#\n# This file gives new contributors an easy way to get everything they need,\n# assuming `cargo` and [Task](https://taskfile.dev) are installed.\n#\n# Structure:\n# - Root (this file): Setup, orchestration, repo-wide tasks (lint, test-all, build-all)\n# - prqlc/: Core development tasks (test, test-all, pull-request)\n# - web/: Web-related tasks (build, run-*)\n#\n# Beyond installing requirements, we generally shouldn't be using this as a\n# Makefile — in other words, we shouldn't require running this as part of normal\n# development. Rust tools are independently good, and adding an intermediate\n# layer means we're reimplementing things or getting in the way. Instead, this\n# can be used to aggregate commands that are currently separate; e.g. check out\n# `test-all`.\n\n# Some of the file may be somewhat over-engineered!\n\nversion: \"3\"\n\nincludes:\n  prqlc:\n    taskfile: ./prqlc\n    dir: ./prqlc\n  zig:\n    taskfile: ./prqlc/bindings/prqlc-c/examples/minimal-zig\n    dir: ./prqlc/bindings/prqlc-c/examples/minimal-zig\n  web:\n    taskfile: ./web\n    dir: ./web\n  python:\n    taskfile: ./prqlc/bindings/prqlc-python\n    dir: ./prqlc/bindings/prqlc-python\n\nvars:\n  # Keep in sync with .vscode/extensions.json\n  vscode_extensions: |\n    budparr.language-hugo-vscode\n    esbenp.prettier-vscode\n    mitsuhiko.insta\n    prql-lang.prql-vscode\n    rust-lang.rust-analyzer\n  cargo_crates: >\n    bacon cbindgen cargo-audit cargo-insta cargo-llvm-cov cargo-release\n    cargo-nextest default-target mdbook mdbook-admonish mdbook-footnote\n    mdbook-toc wasm-bindgen-cli wasm-opt\n  # wasm-pack waiting on https://github.com/rustwasm/wasm-pack/issues/1426\n  #\n  # Excluding `elixir` atm given it's not enabled on Mac and currently unsupported\n  brew_dependencies: |\n    hugo\n    jq\n    npm\n    uv\n    pre-commit\n    python3\n\ntasks:\n  # main installer is \"setup-dev\" which calls other tasks\n  setup-dev:\n    preconditions:\n      - sh: which clang\n        msg: |\n\n          🔴 Can't find `clang`, which is required to install a full development environment (we use `duckdb` in our tests, which requires it).\n\n          Please install it. On macOS, that's\n\n            xcode-select --install\n\n          On Debian Linux, that's\n\n            apt-get update && apt-get install clang\n\n    desc: Install tools for PRQL development.\n    cmds:\n      - task: install-cargo-tools\n      # We only suggest, rather than install; we don't want to intrude (maybe\n      # we're being too conservative?).\n      - cmd: task check-vscode-extensions\n        ignore_error: true\n      - cmd: task check-brew-dependencies\n        ignore_error: true\n      - task: install-maturin\n      - task: install-npm-dependencies\n      - pre-commit install-hooks\n      - rustup component add llvm-tools-preview\n      - cmd: |\n          echo \"\n          🟢 Setup complete! ✅🚀\"\n        silent: true\n\n  install-cargo-tools:\n    desc: Install cargo tools for PRQL development.\n    cmds:\n      # In CI we use `binstall`, because it's faster, and without it we can't\n      # even get the arm64 docker image to build within the GHA timeout. But it\n      # produces lots of confusing warning messages about 429 errors, I think\n      # because it's querying GitHub for so many packages; so we only use it for\n      # CI atm. If the warnings were less alarming, we could use for all\n      # installs.\n      #\n      - |\n        {{ if .CI }}\n          task install-cargo-tools-binstall\n        {{ else }}\n          task install-cargo-tools-source\n        {{ end }}\n\n      - cmd: |\n          [ \"$(which cargo-insta)\" ] || echo \"🔴 Can't find a binary that cargo just installed. Is the cargo bin path (generally at `~/.cargo/bin`) on the \\$PATH?\"\n        silent: true\n\n  install-maturin:\n    desc: Install maturin.\n    # Someone might have this installed with another approach, so only install\n    # if it can't be found.\n    status:\n      - which maturin\n    cmds:\n      - uv tool install maturin\n      - uv tool upgrade maturin\n\n  install-cargo-tools-source:\n    cmds:\n      # `--locked` installs from the underlying lock files (which is not the\n      # default...)\n      - \"cargo install --locked {{.cargo_crates}}\"\n      - cargo install wasm-pack\n\n  install-cargo-tools-binstall:\n    cmds:\n      - cmd: cargo install --locked cargo-binstall\n        platforms: [linux, darwin]\n      - \"cargo binstall -y --locked {{.cargo_crates}}\"\n      - cargo binstall -y wasm-pack\n\n  check-vscode-extensions:\n    desc: Check and suggest VS Code extensions.\n    vars:\n      extensions:\n        # List extensions, or just return true if we can't find `code`.\n        sh: which code && code --list-extensions || true\n      missing_extensions: |\n        {{ range ( .vscode_extensions | trim | splitLines ) -}}\n          {{ if not (contains . $.extensions) }}❌ {{.}} {{else}}✅ {{.}} {{ end }}\n        {{ end -}}\n    status:\n      # If vscode isn't installed, or there are no missing extensions,\n      # return 0 and exit early.\n      - '[ ! -x \"$(which code)\" ] || {{ not (contains \"❌\" .missing_extensions)\n        }}'\n    silent: true\n    cmds:\n      - |\n        echo \"\n\n        🟡 It looks like VS Code is installed but doesn't have all recommended extensions installed:\n        {{ .missing_extensions }}\n\n        Install them with:\n\n          task install-vscode-extensions\n          \"\n      - exit 1\n\n  install-vscode-extensions:\n    desc: Install recommended VS Code extensions.\n    cmds:\n      - |\n        {{ range ( .vscode_extensions | trim | splitLines ) -}}\n          code --install-extension {{.}}\n        {{ end -}}\n\n  check-brew-dependencies:\n    status:\n      - |\n        {{ range  (.brew_dependencies | trim | splitLines) -}}\n          [ -n \"$(which {{ . }})\" ]\n        {{ end -}}\n      - |\n        [ \"$(npm -v | awk -F. '{print ($1 > 9 || ($1 == 9 && $2 > 4)) ? 0 : 1}')\" -eq 0 ]\n    silent: true\n    cmds:\n      - cmd: |\n          echo \"\n\n          🟡 It looks like at least one brew dependency is missing from:\n\n          {{ .brew_dependencies }}\n\n          ...or alternatively that npm < 9.4\n\n          These aren't required for initial PRQL development, but they are required for some of the extras.\n\n          Install them with:\n\n            task install-brew-dependencies\n            \"\n      - exit 1\n\n  install-brew-dependencies:\n    preconditions:\n      - sh: which brew\n        msg: |\n\n\n          🔴 Can't find `brew`, which we use to install {{ .brew_dependencies | trim | splitLines | join \" & \" }}.\n\n          Either install brew & re-run this, or install the dependencies with a different approach, or use PRQL without them.\n          Brew installation instructions at:\n\n            https://brew.sh/\n    status:\n      - task check-brew-dependencies\n    cmds:\n      - brew install {{.brew_dependencies | trim | splitLines | join \" \" }}\n\n  install-npm-dependencies:\n    cmds:\n      - npm install --global prettier prettier-plugin-go-template\n      - cmd:\n          echo \"In order to get nice auto-formatting of web code in VS Code, VS\n          Code requires configuration to use the system-wide install of\n          prettier. See\n          https://github.com/NiklasPor/prettier-plugin-go-template/issues/58#issuecomment-1085060511\n          for more info.\"\n        silent: true\n\n  build-all:\n    desc: Build everything.\n    summary: |\n      Build everything.\n\n      Running this isn't required when developing; it's for caching or as a reference.\n    cmds:\n      - cargo build --all-targets --all-features --quiet\n      - cargo build --all-targets --all-features --target=wasm32-unknown-unknown\n        --quiet\n      # Build without features, as the dependencies have slightly different\n      # features themselves and so require recompiling. This is only useful for\n      # caching.\n      - cargo build --all-targets --quiet\n      - cargo build --all-targets --features=default,test-dbs --quiet\n      - cargo doc --quiet\n      - task: build-each-crate\n      - task: web:build\n      - task: python:build\n\n  build-each-crate:\n    summary: |\n      Builds each crates individually. This is helpful for caching, since often\n      we'll want to run `cargo build -p prqlc`, and the dependencies can have\n      different features for that relative to `cargo build`.\n    vars:\n      PACKAGES:\n        sh:\n          cargo metadata --format-version=1 | jq -r '.workspace_members[] |\n          split(\" \")[0]'\n    preconditions:\n      - sh: command -v jq\n        msg: \"jq is not available. Please install it to continue.\"\n    cmds:\n      - |\n        {{ range ( .PACKAGES | splitLines ) -}}\n        cargo build --all-targets -p {{ . }} --quiet\n        {{ end -}}\n\n  test-rust-api:\n    summary: |\n      Run tests, excluding some internal crates\n    vars:\n      PACKAGES:\n        sh:\n          cargo metadata --format-version=1 | jq -r '.workspace_members[] |\n          split(\" \")[0]'\n    cmds:\n      - |\n        cargo test {{ range without (splitLines .PACKAGES) \"prqlc-parser\" }} -p={{ . }} {{ end }}\n\n  test-all:\n    desc: Test everything across all workspaces, accepting snapshots.\n    summary: |\n      Test everything, accepting snapshots.\n\n      Running this isn't required when developing; it's for caching or as a reference.\n\n    cmds:\n      # TODO:\n      # - We could add `prqlc-c` here.\n      # - We deliberately don't test some other bindings, such as `prql-php`,\n      #   given they require more dependencies and aren't yet Supported. They\n      #   run in CI.\n      - task: prqlc:pull-request\n      - task: test-js\n      # No nextest here as doesn't work with wasm\n      - cargo test --target=wasm32-unknown-unknown\n      - task: python:test\n      - task: build-all\n\n  test-rust-coverage:\n    desc: Run tests with instrumentation for code coverage.\n    summary:\n      Run tests with instrumentation for code coverage. This works with VS\n      Code's [Coverage\n      Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters)\n      extension.\n      # We previously had a different target dir, but the default seems to use `target/llvm-cov` as its target already, so we can remove this assuming it works OK\n      # env:\n      # CARGO_TARGET_DIR: \"./target-cov\"\n    cmds:\n      - cargo llvm-cov --lcov --output-path lcov.info\n        --features=default,test-dbs\n\n  lint:\n    desc: Run all lints (pre-commit & cargo clippy).\n    cmds:\n      - pre-commit run --all-files\n      - cargo clippy --all-targets --all-features\n\n  test-rust-external-dbs:\n    desc: Run tests which require external databases, with docker\n    cmds:\n      - cmd:\n          echo \"🔵 Starting docker containers. In some circumstances the tests\n          will start before images are ready. After one initial failure, try\n          re-running.\"\n        silent: true\n      - cd prqlc/prqlc/tests/integration/dbs && docker compose up --wait\n      # This _only_ runs the external dbs tests, but we could make it run\n      # everything?\n      - cargo test --features=test-dbs-external --test=integration --\n        --nocapture queries::results\n      # We could run this ourselves, but makes iteration times much longer\n      - cmd: |\n          echo \"🔵 To remove containers and remove local built images, run\n\n          cd prqlc/prqlc/tests/integration/dbs && docker compose down --rmi local\"\n        silent: true\n\n  test-js:\n    dir: prqlc/bindings/js\n    cmds:\n      - npm cit\n\n  # Currently disabled; see prql-elixir/README.md for details\n  test-elixir:\n    dir: prqlc/bindings/elixir\n    cmds:\n      # We could move this line into an `install` task\n      - mix local.hex --force\n      - mix deps.get --force\n      - mix compile\n      - mix test\n\n  build-php:\n    - cargo build --package prqlc-c --release\n    - mkdir -p prqlc/bindings/php/lib/\n    - cp target/release/libprqlc_c.* prqlc/bindings/prqlc-c/prqlc.h\n      prqlc/bindings/php/lib/\n    - cd prqlc/bindings/php && composer install\n\n  build-prqlc-c-header:\n    desc: Build the C header for the C bindings.\n    dir: prqlc/bindings/prqlc-c\n    cmds:\n      - cbindgen --crate prqlc-c --output prqlc.h\n      - cbindgen --crate prqlc-c --lang C++ --output prqlc.hpp\n\n  test-php:\n    dir: prqlc/bindings/php\n    cmds:\n      - vendor/bin/phpunit tests\n# The next two tasks are not used for either:\n#  - the Dockerfile (installing brew takes forever)\n#    so the Dockerfile simply installs hugo & nodejs directly\n#  - the \"desktop setup\" - it uses other tasks in the Taskfile.yaml\n# They remain in the Taskfile.yaml as a hint if they should ever be needed\n\n# install-hugo:\n#   cmds:\n#     # - /home/linuxbrew/.linuxbrew/bin/brew install hugo\n#     - curl -L https://github.com/gohugoio/hugo/releases/download/v0.91.2/hugo_0.91.2_Linux-64bit.deb -o hugo.deb\n#     - apt install ./hugo.deb\n\n# install-nodejs:\n#   cmds:\n#     # - /home/linuxbrew/.linuxbrew/bin/brew install nodejs\n#     - curl -fsSL https://deb.nodesource.com/setup_16.x | bash -\n#     - apt install -y nodejs\n#     - cd /app/playground/ ; npm install\n"
  },
  {
    "path": "bacon.toml",
    "content": "# Initial bacon config file; edits and contributions welcome.\n\ndefault_job = \"clippy\"\n\n# PRQL additions\n[jobs.test]\ncommand = ['cargo', 'insta', 'test', \"--color=always\", \"--features=default,test-dbs\"]\n\n[jobs.test-accept]\ncommand = ['cargo', 'insta', 'test', '--accept', \"--color=always\", \"--features=default,test-dbs\", \"--unreferenced=auto\"]\nwatch = [\"*\"]\n\n[jobs.test-accept-fast]\ncommand = ['cargo', 'insta', 'test', '--accept', \"--color=always\", \"-p=prqlc\", \"--lib\"]\nwatch = [\"*\"]\n\n# Standard tasks\n\n[jobs.check]\ncommand = [\"cargo\", \"check\", \"--color=always\"]\nneed_stdout = false\nwatch = [\"*\"]\n\n[jobs.check-all]\ncommand = [\"cargo\", \"check\", \"--all-targets\", \"--all-features\", \"--color=always\"]\nneed_stdout = false\nwatch = [\"*\"]\n\n[jobs.clippy]\ncommand = [\"cargo\", \"clippy\", \"--all-targets\", \"--all-features\", \"--color=always\"]\nneed_stdout = false\nwatch = [\"*\"]\n\n[jobs.test-cargo]\ncommand = [\"cargo\", \"test\", \"--color=always\", \"--no-fail-fast\"]\nneed_stdout = true\nwatch = [\"*\"]\n\n[jobs.doc]\ncommand = [\"cargo\", \"doc\", \"--color=always\", \"--no-deps\"]\nneed_stdout = false\n\n# If the doc compiles, then it opens in your browser and bacon switches\n# to the previous job\n[jobs.doc-open]\ncommand = [\"cargo\", \"doc\", \"--color\", \"always\", \"--no-deps\", \"--open\"]\nneed_stdout = false\non_success = \"back\" # so that we don't open the browser at each change\n\n# You may define here keybindings that would be specific to\n# a project, for example a shortcut to launch a specific job.\n# Shortcuts to internal functions (scrolling, toggling, etc.)\n# should go in your personal prefs.toml file instead.\n[keybindings]\na = \"job:test-accept\"\nc = \"job:clippy\"\nd = \"job:doc-open\"\nf = \"job:test-accept-fast\"\n# `g` for no insta; bacon is better at displaying errors, although have been\n# trying to work through why the errors from insta aren't picked up: https://github.com/rust-lang/cargo/issues/12220\ng = \"job:test-cargo\"\nr = \"job:run\"\nt = \"job:test\"\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"PRQL development environment\";\n\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs\";\n    flake-utils.url = \"github:numtide/flake-utils\";\n    naersk.url = \"github:nix-community/naersk\";\n    mdbook-footnote = {\n      url = \"github:aljazerzen/mdbook-footnote\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.flake-utils.follows = \"flake-utils\";\n      inputs.naersk.follows = \"naersk\";\n    };\n    hyperlink = {\n      url = \"github:aljazerzen/hyperlink\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n      inputs.flake-utils.follows = \"flake-utils\";\n      inputs.naersk.follows = \"naersk\";\n    };\n    fenix = {\n      url = \"github:nix-community/fenix\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n  };\n\n  outputs = { self, nixpkgs, flake-utils, mdbook-footnote, hyperlink, fenix, naersk }:\n    flake-utils.lib.eachDefaultSystem (system:\n      let\n        pkgs = nixpkgs.legacyPackages.${system};\n        fenix_pkgs = fenix.packages.${system};\n\n        essentials = with pkgs; [\n          # rust toolchain\n          (fenix_pkgs.combine [\n            (fenix_pkgs.fromToolchainFile {\n              file = ./rust-toolchain.toml;\n              sha256 = \"sha256-s1RPtyvDGJaX/BisLT+ifVfuhDT1nZkZ1NcK8sbwELM=\";\n            })\n            (fenix_pkgs.stable.withComponents [\n              \"cargo\"\n              \"clippy\"\n              \"rust-src\"\n              \"rustc\"\n              \"rustfmt\"\n              \"rust-analyzer\"\n              \"llvm-tools-preview\"\n            ])\n          ])\n\n          # tooling\n          clang # for llvm debugger in VSCode\n\n          # tools\n          cargo-nextest\n          bacon\n          cargo-audit\n          cargo-insta\n          cargo-release\n          pkg-config\n          openssl\n          cargo-llvm-cov\n\n          # actions\n          go-task\n          sd\n          ripgrep\n          nodePackages.prettier\n          #nodePackages.prettier-plugin-go-template\n          #nixpkgs-fmt\n          rsync\n        ];\n\n        web = with pkgs; [\n          # book\n          mdbook\n          mdbook-admonish\n          mdbook-footnote.defaultPackage.${system}\n\n          # website\n          hugo\n\n          # playground\n          nodejs\n          nodePackages.npm\n\n          # link check\n          hyperlink.defaultPackage.${system}\n        ];\n\n        bindings = with pkgs; [\n          # bindings\n          python311\n          zlib\n          maturin\n          ruff\n          black\n\n          wasm-bindgen-cli\n          wasm-pack\n        ];\n      in\n      {\n        devShells.default = pkgs.mkShell {\n          buildInputs = essentials;\n        };\n        devShells.web = pkgs.mkShell {\n          buildInputs = essentials ++ web;\n        };\n        devShells.full = pkgs.mkShell {\n          buildInputs = essentials ++ web ++ bindings;\n\n          # needed for running wheels produced by Python maturin builds that are not manylinux\n          # shellHook = ''\n          #   export LD_LIBRARY_PATH=\"${pkgs.lib.makeLibraryPath bindings}:$LD_LIBRARY_PATH\"\n          #   export LD_LIBRARY_PATH=\"${pkgs.stdenv.cc.cc.lib.outPath}/lib:$LD_LIBRARY_PATH\"\n          # '';\n        };\n      });\n}\n"
  },
  {
    "path": "grammars/CotEditor/PRQL.yaml",
    "content": "---\nkind: code\nextensions:\n  - keyString: prql\nfilenames: []\nmetadata:\n  author: vanillajonathan\n  description:\n    PRQL is a modern language for transforming data — a simple, powerful,\n    pipelined SQL replacement\n  distributionURL: https://coteditor.com\n  lastModified: 2023-12-29\n  license: Same as CotEditor\n  version: 0.0.1\noutlineMenu: []\nattributes:\n  - beginString: \"@{\\\\w+(=\\\\w+)?}\"\n    regularExpression: true\nkeywords:\n  - beginString: aggregate\n  - beginString: derive\n  - beginString: filter\n  - beginString: from\n  - beginString: group\n  - beginString: join\n  - beginString: select\n  - beginString: sort\n  - beginString: take\n  - beginString: window\n  - beginString: case\n  - beginString: let\n  - beginString: module\n  - beginString: prql\ncommands:\n  - beginString: abs\n  - beginString: any\n  - beginString: average\n  - beginString: concat_array\n  - beginString: count\n  - beginString: every\n  - beginString: min\n  - beginString: max\n  - beginString: stddev\n  - beginString: sum\n  - beginString: read_csv\n  - beginString: read_json\n  - beginString: read_parquet\n  - beginString: all\n  - beginString: map\n  - beginString: zip\n  - beginString: from_text\n  - beginString: lower\n  - beginString: upper\n  - beginString: lead\n  - beginString: lag\n  - beginString: first\n  - beginString: last\n  - beginString: rank\n  - beginString: rank_dense\n  - beginString: row_number\ntypes:\n  - beginString: bool\n  - beginString: float\n  - beginString: int\n  - beginString: int8\n  - beginString: int16\n  - beginString: int32\n  - beginString: int64\n  - beginString: int128\n  - beginString: time\n  - beginString: timestamp\n  - beginString: text\n  - beginString: date\n  - beginString: math\nvariables: []\nvalues:\n  - beginString: \"true\"\n  - beginString: \"false\"\n  - beginString: \"null\"\nnumbers:\n  - beginString: ((?<!\\w)|[-+])(?:\\.[0-9][0-9_]*|[0-9][0-9_]*\\.|[0-9]+\\.[0-9][0-9_]*|[0-9][0-9_]*)(e[+-]?[0-9][0-9_]*)?[jl]?\n    ignoreCase: true\n    regularExpression: true\n    description: decimal\n  - beginString: \\b0b[01][01_]*l?\n    ignoreCase: true\n    regularExpression: true\n    description: binary\n  - beginString: \\b0o[0-7][0-7_]*l?\n    ignoreCase: true\n    regularExpression: true\n    description: octal\n  - beginString: \\b0x[0-9a-f][0-9a-f_]*l?\n    ignoreCase: true\n    regularExpression: true\n    description: hexadecimal\nstrings:\n  - beginString: '\"'\n    endString: '\"'\n  - beginString: '\"\"\"'\n    endString: '\"\"\"'\n  - beginString: \"'\"\n    endString: \"'\"\n  - beginString: \"'''\"\n    endString: \"'''\"\n  - beginString: \\b(r|f|s|)['\"]\n    ignoreCase: true\n    regularExpression: true\n  - beginString: \"`\"\n    endString: \"`\"\ncharacters: []\ncomments: []\ncommentDelimiters:\n  inlineDelimiter: \"#\"\n"
  },
  {
    "path": "grammars/CotEditor/README.md",
    "content": "# PRQL syntax style for CotEditor\n\n[CotEditor](https://coteditor.com/)'s syntax style file for the PRQL query\nlanguage.\n\n## Install\n\n1. Go to _Preferences > Format_ and choose _Import…_ in the gear icon menu that\n   is just below the installed style list.\n2. Choose the `PRQL.yaml` file.\n"
  },
  {
    "path": "grammars/GtkSourceView/README.md",
    "content": "# Syntax highlighting for GtkSourceView\n\nThis is a syntax highlighting file the\n[GtkSourceView](https://gitlab.gnome.org/GNOME/gtksourceview) component used by\ntext editors and integrated development environments such as\n[GNOME Text Editor](https://apps.gnome.org/TextEditor/) and\n[GNOME Builder](https://apps.gnome.org/Builder/).\n\n## Installation\n\nTo install system-wide, copy the `prql.xml` file to:\n\n    /usr/share/gtksourceview-5/language-specs/\n\nTo install for the current user, copy the `prql.xml` file to:\n\n    ~/.local/share/gtksourceview-5/language-specs/\n\n## Embedding\n\nTo embed it in your GTK application using the `GtkSourceView` widget add it to a\n[`LanguageManager`](https://gnome.pages.gitlab.gnome.org/gtksourceview/gtksourceview5/class.LanguageManager.html)\nwhich you add to your\n[`Buffer`](https://gnome.pages.gitlab.gnome.org/gtksourceview/gtksourceview5/method.Buffer.set_language.html).\n"
  },
  {
    "path": "grammars/GtkSourceView/prql.lang",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n\n Copyright (C) 2024 The PRQL Project\n\n https://prql-lang.org/\n https://github.com/PRQL/prql\n\n-->\n<language id=\"prql\" name=\"PRQL\" version=\"2.0\" _section=\"Source\">\n  <metadata>\n    <property name=\"mimetypes\">application/x.prql;application/prs.prql;application/vnd.prql</property>\n    <property name=\"globs\">*.prql</property>\n    <property name=\"line-comment-start\">#</property>\n    <property name=\"suggested-suffix\">.prql</property>\n  </metadata>\n\n  <styles>\n    <style id=\"base-n-integer\"  name=\"Base-N number\"     map-to=\"def:base-n-integer\"/>\n    <style id=\"boolean\"         name=\"Boolean value\"     map-to=\"def:boolean\"/>\n    <style id=\"built-in-object\" name=\"Built-in object\"   map-to=\"def:builtin\"/>\n    <style id=\"comment\"         name=\"Comment\"           map-to=\"def:comment\"/>\n    <style id=\"declaration\"     name=\"Declarations\"      map-to=\"def:type\"/>\n    <style id=\"escaped-char\"    name=\"Escaped Character\" map-to=\"def:special-char\"/>\n    <style id=\"floating-point\"  name=\"Floating Point\"    map-to=\"def:floating-point\"/>\n    <style id=\"string\"          name=\"String\"            map-to=\"def:string\"/>\n    <style id=\"keyword\"         name=\"Keyword\"           map-to=\"def:keyword\"/>\n    <style id=\"function\"        name=\"Function\"          map-to=\"def:function\"/>\n    <style id=\"decimal\"         name=\"Decimal\"           map-to=\"def:decimal\"/>\n    <style id=\"type\"            name=\"Data Type\"         map-to=\"def:type\"/>\n    <style id=\"f-string-curly-braces\" name=\"f-string curly braces\" map-to=\"def:special-char\"/>\n    <style id=\"unicode-bdi\"    name=\"Unicode BDI\"       map-to=\"def:error\"/>\n  </styles>\n\n  <definitions>\n\n    <context id=\"boolean\" style-ref=\"boolean\">\n      <keyword>false</keyword>\n      <keyword>true</keyword>\n    </context>\n\n    <context id=\"declarations\" style-ref=\"declaration\">\n      <keyword>let</keyword>\n    </context>\n\n    <context id=\"unicode-bdi\" style-ref=\"unicode-bdi\">\n      <match>(\\u202A|\\u202B|\\u202D|\\u202E|\\u2066|\\u2067|\\u2068|\\u202C|\\u2069)</match>\n    </context>\n\n    <context id=\"illegal-string\" style-ref=\"unicode-bdi\">\n      <match>\\b[^frs:][\\\"\\']</match>\n    </context>\n\n    <context id=\"psql-datatypes\" style-ref=\"type\">\n      <keyword>bool</keyword>\n      <keyword>float</keyword>\n      <keyword>int</keyword>\n      <keyword>int8</keyword>\n      <keyword>int16</keyword>\n      <keyword>int32</keyword>\n      <keyword>int64</keyword>\n      <keyword>int128</keyword>\n      <keyword>text</keyword>\n      <keyword>date</keyword>\n      <keyword>time</keyword>\n      <keyword>timestamp</keyword>\n    </context>\n\n    <context id=\"integer-literals\" style-ref=\"decimal\">\n      <match>\\b[0-9_]+(?![Ee][\\+\\-]?[0-9]*)\\b</match>\n    </context>\n\n    <context id=\"number-literals\" style-ref=\"floating-point\">\n      <match>(?&lt;![\\w\\.])(([0-9]+\\.[0-9]*|\\.[0-9]+)([Ee][\\+\\-]?[0-9]*)?|([0-9]+[Ee][\\+\\-]?[0-9]*))(?![\\w\\.])</match>\n    </context>\n\n    <context id=\"null\" style-ref=\"decimal\">\n      <keyword>null</keyword>\n    </context>\n\n    <context id=\"block-comment\" style-ref=\"comment\" class=\"comment\" end-at-line-end=\"true\" class-disabled=\"no-spell-check\">\n      <start>#!</start>\n      <include>\n        <context ref=\"def:in-line-comment\"/>\n      </include>\n    </context>\n\n    <context id=\"line-comment\" style-ref=\"comment\" end-at-line-end=\"true\" class=\"comment\" class-disabled=\"no-spell-check\">\n      <start>#</start>\n      <include>\n        <context ref=\"def:in-line-comment\"/>\n      </include>\n    </context>\n\n    <context id=\"built-in-object\" style-ref=\"built-in-object\">\n      <keyword>date</keyword>\n      <keyword>math</keyword>\n      <keyword>prql</keyword>\n    </context>\n\n    <!-- https://prql-lang.org/book/reference/stdlib/ -->\n    <context id=\"aggregate-functions\" style-ref=\"function\">\n      <keyword>any</keyword>\n      <keyword>average</keyword>\n      <keyword>concat_array</keyword>\n      <keyword>count</keyword>\n      <keyword>every</keyword>\n      <keyword>max|min</keyword>\n      <keyword>stddev</keyword>\n      <keyword>sum</keyword>\n    </context>\n\n    <context id=\"file-reading-functions\" style-ref=\"function\">\n      <keyword>read_csv</keyword>\n      <keyword>read_json</keyword>\n      <keyword>read_parquet</keyword>\n    </context>\n\n    <context id=\"list-functions\" style-ref=\"function\">\n      <keyword>all</keyword>\n      <keyword>map</keyword>\n      <keyword>zip</keyword>\n      <keyword>_eq</keyword>\n      <keyword>_is_null</keyword>\n    </context>\n\n    <context id=\"misc-functions\" style-ref=\"function\">\n      <keyword>from_text</keyword>\n    </context>\n\n    <context id=\"text-functions\" style-ref=\"function\">\n      <keyword>contains</keyword>\n      <keyword>ends_with</keyword>\n      <keyword>extract</keyword>\n      <keyword>length</keyword>\n      <keyword>lower</keyword>\n      <keyword>ltrim</keyword>\n      <keyword>replace</keyword>\n      <keyword>rtrim</keyword>\n      <keyword>starts_with</keyword>\n      <keyword>trim</keyword>\n      <keyword>upper</keyword>\n    </context>\n\n    <context id=\"window-functions\" style-ref=\"function\">\n      <keyword>lag|lead</keyword>\n      <keyword>first|last</keyword>\n      <keyword>rank</keyword>\n      <keyword>rank_dense</keyword>\n      <keyword>row_number</keyword>\n    </context>\n\n    <context id=\"transform-type-definitions\" style-ref=\"function\">\n      <keyword>aggregate</keyword>\n      <keyword>derive</keyword>\n      <keyword>filter</keyword>\n      <keyword>from</keyword>\n      <keyword>group</keyword>\n      <keyword>join</keyword>\n      <keyword>select</keyword>\n      <keyword>sort</keyword>\n      <keyword>take</keyword>\n      <keyword>window</keyword>\n    </context>\n\n    <context id=\"date-functions\" style-ref=\"function\">\n      <keyword>to_text</keyword>\n    </context>\n\n    <context id=\"math-functions\" style-ref=\"function\">\n      <keyword>abs</keyword>\n      <keyword>acos</keyword>\n      <keyword>asin</keyword>\n      <keyword>atan</keyword>\n      <keyword>ceil</keyword>\n      <keyword>cos</keyword>\n      <keyword>degrees</keyword>\n      <keyword>exp</keyword>\n      <keyword>floor</keyword>\n      <keyword>ln</keyword>\n      <keyword>log</keyword>\n      <keyword>log10</keyword>\n      <keyword>pi</keyword>\n      <keyword>pow</keyword>\n      <keyword>radians</keyword>\n      <keyword>round</keyword>\n      <keyword>sin</keyword>\n      <keyword>sqrt</keyword>\n      <keyword>tan</keyword>\n    </context>\n\n    <define-regex id=\"identifier\" extended=\"true\">\n      (?&gt; (?: _ | \\%{def:unicode-xid-start} ) \\%{def:unicode-xid-continue}* )\n    </define-regex>\n    <define-regex id=\"number\">[1-9][0-9]*</define-regex>\n\n    <!-- https://prql-lang.org/book/reference/syntax/strings.html -->\n    <context id=\"escaped-char\" style-ref=\"escaped-char\" extend-parent=\"true\">\n      <match extended=\"true\">\n        \\\\(                   # leading backslash\n        [\\\\'\"bfnrt]         | # single escaped char\n        u{[0-9A-Fa-f]{1,6}} | # \\u{hhhhhh} - unicode character\n        x[0-9A-Fa-f]{1,2}   | # \\xhh - character with hex value hh\n        )\n      </match>\n    </context>\n\n    <context id=\"curly-braces\" extend-parent=\"true\">\n      <start>\\{</start>\n      <end>\\}</end>\n      <include>\n        <context ref=\"prql\"/>\n        <context ref=\"curly-braces\"/>\n      </include>\n    </context>\n\n    <context id=\"f-string-curly-braces\" extend-parent=\"false\" class-disabled=\"string\">\n      <start>(\\{)</start>\n      <end>(\\})</end>\n      <include>\n        <context ref=\"prql\"/>\n        <context ref=\"curly-braces\"/>\n        <context sub-pattern=\"1\" where=\"start\" style-ref=\"f-string-curly-braces\"/>\n        <context sub-pattern=\"1\" where=\"end\" style-ref=\"f-string-curly-braces\"/>\n      </include>\n    </context>\n\n    <context id=\"escaped-curly-brace\" style-ref=\"escaped-char\" extend-parent=\"true\">\n      <match>\\{\\{</match>\n    </context>\n\n    <context id=\"double-quoted-string\" style-ref=\"string\" end-at-line-end=\"true\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>\"</start>\n      <end>\"</end>\n      <include>\n        <context ref=\"escaped-char\"/>\n        <context ref=\"def:line-continue\"/>\n      </include>\n    </context>\n\n    <context id=\"single-quoted-string\" style-ref=\"string\" end-at-line-end=\"true\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>'</start>\n      <end>'</end>\n      <include>\n        <context ref=\"escaped-char\"/>\n        <context ref=\"def:line-continue\"/>\n      </include>\n    </context>\n\n    <context id=\"double-quoted-triple-string\" style-ref=\"string\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>\"\"\"</start>\n      <end>\"\"\"</end>\n      <include>\n        <context ref=\"escaped-char\"/>\n        <context ref=\"def:line-continue\"/>\n      </include>\n    </context>\n\n    <context id=\"single-quoted-triple-string\" style-ref=\"string\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>'''</start>\n      <end>'''</end>\n      <include>\n        <context ref=\"escaped-char\"/>\n        <context ref=\"def:line-continue\"/>\n      </include>\n    </context>\n\n    <context id=\"double-quoted-f-string\" end-at-line-end=\"true\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>(f\")</start>\n      <end>(\")</end>\n      <include>\n        <context ref=\"escaped-curly-brace\"/>\n        <context ref=\"f-string-curly-braces\"/>\n        <context ref=\"escaped-char\"/>\n        <context ref=\"def:line-continue\"/>\n        <context style-ref=\"string\" extend-parent=\"false\" class=\"string\">\n          <match>.</match>\n        </context>\n        <context sub-pattern=\"1\" where=\"start\" style-ref=\"string\"/>\n        <context sub-pattern=\"1\" where=\"end\" style-ref=\"string\"/>\n      </include>\n    </context>\n\n    <context id=\"single-quoted-f-string\" end-at-line-end=\"true\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>(f')</start>\n      <end>(')</end>\n      <include>\n        <context ref=\"escaped-curly-brace\"/>\n        <context ref=\"f-string-curly-braces\"/>\n        <context ref=\"escaped-char\"/>\n        <context ref=\"def:line-continue\"/>\n        <context style-ref=\"string\" extend-parent=\"false\" class=\"string\">\n          <match>.</match>\n        </context>\n        <context sub-pattern=\"1\" where=\"start\" style-ref=\"string\"/>\n        <context sub-pattern=\"1\" where=\"end\" style-ref=\"string\"/>\n      </include>\n    </context>\n\n    <context id=\"double-quoted-r-string\" style-ref=\"string\" end-at-line-end=\"true\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>r\"</start>\n      <end>\"</end>\n      <include>\n        <context ref=\"def:line-continue\"/>\n      </include>\n    </context>\n\n    <context id=\"single-quoted-r-string\" style-ref=\"string\" end-at-line-end=\"true\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>r'</start>\n      <end>r'</end>\n      <include>\n        <context ref=\"def:line-continue\"/>\n      </include>\n    </context>\n\n    <context id=\"double-quoted-s-string\" style-ref=\"string\" end-at-line-end=\"true\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>s\"</start>\n      <end>\"</end>\n      <include>\n        <context ref=\"def:line-continue\"/>\n      </include>\n    </context>\n\n    <context id=\"single-quoted-s-string\" style-ref=\"string\" end-at-line-end=\"true\" class=\"string\" class-disabled=\"no-spell-check\">\n      <start>s'</start>\n      <end>'</end>\n      <include>\n        <context ref=\"def:line-continue\"/>\n      </include>\n    </context>\n\n    <context id=\"dimension\" style-ref=\"floating-point\">\n      <prefix>\\%{number}</prefix>\n      <keyword>microseconds</keyword>\n      <keyword>milliseconds</keyword>\n      <keyword>seconds</keyword>\n      <keyword>minutes</keyword>\n      <keyword>hours</keyword>\n      <keyword>days</keyword>\n      <keyword>weeks</keyword>\n      <keyword>months</keyword>\n      <keyword>years</keyword>\n    </context>\n\n    <context id=\"prql\" class=\"no-spell-check\">\n      <include>\n        <context ref=\"boolean\"/>\n\n        <context id=\"binary\" style-ref=\"base-n-integer\">\n          <match>(?&lt;![\\w\\.])0[bB](_?[0-1])+(?![\\w\\.])</match>\n        </context>\n\n        <context id=\"octal\" style-ref=\"base-n-integer\">\n          <match>(?&lt;![\\w\\.])0[oO](_?[0-7])+(?![\\w\\.])</match>\n        </context>\n\n        <context id=\"hex\" style-ref=\"base-n-integer\">\n          <match>(?&lt;![\\w\\.])0[xX](_?[0-9A-Fa-f])+(?![\\w\\.])</match>\n        </context>\n\n        <context ref=\"declarations\"/>\n        <context ref=\"dimension\"/>\n        <context ref=\"double-quoted-triple-string\"/>\n        <context ref=\"single-quoted-triple-string\"/>\n        <context ref=\"double-quoted-f-string\"/>\n        <context ref=\"single-quoted-f-string\"/>\n        <context ref=\"double-quoted-r-string\"/>\n        <context ref=\"single-quoted-r-string\"/>\n        <context ref=\"double-quoted-s-string\"/>\n        <context ref=\"single-quoted-s-string\"/>\n        <context ref=\"double-quoted-string\"/>\n        <context ref=\"single-quoted-string\"/>\n        <context ref=\"psql-datatypes\"/>\n        <context ref=\"number-literals\"/>\n        <context ref=\"integer-literals\"/>\n        <context ref=\"null\"/>\n        <context ref=\"block-comment\"/>\n        <context ref=\"line-comment\"/>\n        <context ref=\"built-in-object\"/>\n        <context ref=\"aggregate-functions\"/>\n        <context ref=\"date-functions\"/>\n        <context ref=\"file-reading-functions\"/>\n        <context ref=\"list-functions\"/>\n        <context ref=\"math-functions\"/>\n        <context ref=\"misc-functions\"/>\n        <context ref=\"text-functions\"/>\n        <context ref=\"transform-type-definitions\"/>\n        <context ref=\"unicode-bdi\"/>\n        <context ref=\"illegal-string\"/>\n        <context ref=\"window-functions\"/>\n      </include>\n    </context>\n\n  </definitions>\n</language>\n"
  },
  {
    "path": "grammars/KSyntaxHighlighting/README.md",
    "content": "# Syntax highlighting for KSyntaxHighlighting\n\nThis is a syntax highlighting file the\n[KSyntaxHighlighting](https://invent.kde.org/frameworks/syntax-highlighting)\ncomponent used by text editors and integrated development environments such as\n[Kate](https://kate-editor.org/), [KWrite](https://apps.kde.org/kwrite/) and\n[KDevelop](https://kdevelop.org/).\n\n## Installation\n\nTo install for the current user, copy the `prql.xml` file to:\n\n| System               | Path                                                                               |\n| -------------------- | ---------------------------------------------------------------------------------- |\n| For local user       | `$HOME/.local/share/org.kde.syntax-highlighting/syntax/`                           |\n| For Flatpak packages | `$HOME/.var/app/PACKAGE_NAME/data/org.kde.syntax-highlighting/syntax/`             |\n| For Snap packages    | `$HOME/snap/PACKAGE_NAME/current/.local/share/org.kde.syntax-highlighting/syntax/` |\n| On Windows           | `%USERPROFILE%\\AppData\\Local\\org.kde.syntax-highlighting\\syntax\\`                  |\n| On macOS             | `$HOME/Library/Application Support/org.kde.syntax-highlighting/syntax/`            |\n\nFor Flatpak and Snap the PACKAGE_NAME is something like `org.kde.kate`,\n`org.kde.kwrite` or `org.kde.kdevelop`.\n"
  },
  {
    "path": "grammars/KSyntaxHighlighting/prql.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE language\n[\n  <!ENTITY digitPart \"[0-9](?:_?[0-9])*\">\n  <!ENTITY beforeDigit \"(?&lt;![\\.\\w[:^ascii:]])\">\n  <!ENTITY beforePointFloat \"(?&lt;![\\w[:^ascii:]])\">\n\n  <!ENTITY escapedHex \"\\\\x[0-9A-Fa-f]{2}\">\n  <!ENTITY escapedHexUni \"&escapedHex;|\\\\u\\{[0-9A-Fa-f]{1,6}\\}\">\n]\n>\n<!-- PRQL https://prql-lang.org/ -->\n<language name=\"PRQL\" version=\"0\" kateversion=\"5.0\" section=\"Database\" extensions=\"*.prql\" author=\"vanillajonathan\" license=\"MIT\">\n  <highlighting>\n    <list name=\"imports\">\n      <item>module</item>\n    </list>\n\n    <list name=\"builtinfuncs\">\n      <item>aggregate</item>\n      <item>derive</item>\n      <item>filter</item>\n      <item>from</item>\n      <item>group</item>\n      <item>join</item>\n      <item>select</item>\n      <item>sort</item>\n      <item>take</item>\n      <item>window</item>\n    </list>\n\n    <list name=\"specialvars\">\n      <item>null</item>\n      <item>this</item>\n      <item>that</item>\n      <item>true</item>\n      <item>false</item>\n    </list>\n\n    <list name=\"declarations\">\n      <item>type</item>\n      <item>alias</item>\n    </list>\n\n    <list name=\"types\">\n      <item>bool</item>\n      <item>float</item>\n      <item>int</item>\n      <item>int8</item>\n      <item>int16</item>\n      <item>int32</item>\n      <item>int64</item>\n      <item>int128</item>\n      <item>text</item>\n      <item>date</item>\n      <item>time</item>\n      <item>timestamp</item>\n    </list>\n\n    <list name=\"letExpressions\">\n      <item>let</item>\n      <item>in</item>\n    </list>\n\n    <!-- Built-in modules -->\n    <list name=\"modules_builtin\">\n      <item>date</item>\n      <item>math</item>\n      <item>text</item>\n      <item>prql</item>\n    </list>\n\n    <!-- Methods of the date module -->\n    <list name=\"date_functions\">\n      <item>to_text</item>\n    </list>\n\n    <!-- Methods of the math module -->\n    <list name=\"math_functions\">\n      <item>abs</item>\n      <item>acos</item>\n      <item>asin</item>\n      <item>atan</item>\n      <item>ceil</item>\n      <item>cos</item>\n      <item>degrees</item>\n      <item>exp</item>\n      <item>floor</item>\n      <item>ln</item>\n      <item>log</item>\n      <item>log10</item>\n      <item>pi</item>\n      <item>pow</item>\n      <item>radians</item>\n      <item>round</item>\n      <item>sin</item>\n      <item>sqrt</item>\n      <item>tan</item>\n    </list>\n\n    <!-- Methods of the text module -->\n    <list name=\"text_functions\">\n      <item>lower</item>\n      <item>upper</item>\n      <item>ltrim</item>\n      <item>rtrim</item>\n      <item>trim</item>\n      <item>length</item>\n      <item>extract</item>\n      <item>replace</item>\n      <item>starts_with</item>\n      <item>contains</item>\n      <item>ends_with</item>\n    </list>\n\n    <list name=\"durations\">\n      <item>microseconds</item>\n      <item>milliseconds</item>\n      <item>seconds</item>\n      <item>minutes</item>\n      <item>hours</item>\n      <item>days</item>\n      <item>weeks</item>\n      <item>months</item>\n      <item>years</item>\n    </list>\n\n    <contexts>\n      <context name=\"Normal\" attribute=\"Normal Text\" lineEndContext=\"#stay\">\n        <DetectChar attribute=\"Comment\" char=\"#\" context=\"Hash comment\"/>\n\n        <keyword attribute=\"Data Type\" context=\"#stay\" String=\"types\" />\n        <keyword attribute=\"Special Variable\" String=\"specialvars\" context=\"#stay\"/>\n        <keyword attribute=\"Builtin Function\" String=\"builtinfuncs\" context=\"#stay\"/>\n        <keyword attribute=\"Keyword\"          context=\"#stay\" String=\"declarations\" />\n        <keyword attribute=\"Keyword\"          context=\"#stay\" String=\"letExpressions\" />\n        <keyword attribute=\"Keyword\"          context=\"#stay\" String=\"imports\" />\n\n        <IncludeRules context=\"Number\" />\n        <IncludeRules context=\"StringVariants\" />\n\n        <RegExpr attribute=\"Annotation\" String=\"@\\{.*\\}\" firstNonSpace=\"true\"/>\n        <RegExpr attribute=\"Operator\"         context=\"#stay\" String=\"-&gt;|::|\\/\\/|\\.\\.|&amp;&amp;|\\|\\||\\+\\+|\\|&gt;|&lt;\\||&gt;&gt;|&lt;&lt;|==|\\/=|&lt;=|&gt;=|[+-\\/*%=&gt;&lt;^\\|!@#$&amp;~?]\" />\n\n        <Int        attribute=\"Decimal\" context=\"#stay\" />\n        <RegExpr    attribute=\"Hex\"     context=\"#stay\" String=\"0x[\\da-f]+\" insensitive=\"true\" />\n        <RegExpr    attribute=\"Float\"   context=\"#stay\" String=\"\\d+\\.\\d+(e[+-]?\\d+)?\" insensitive=\"true\" />\n      </context>\n\n      <!-- math module -->\n      <context name=\"FindMemberModuleMath\" attribute=\"Normal Text\" lineEndContext=\"#pop\" fallthrough=\"true\" fallthroughContext=\"#pop!NoRegExp\">\n        <DetectSpaces />\n        <DetectChar context=\"#pop!MemberModuleMath\" attribute=\"Symbol\" char=\".\" />\n      </context>\n      <context name=\"MemberModuleMath\" attribute=\"Normal Text\" lineEndContext=\"#pop\" fallthrough=\"true\" fallthroughContext=\"#pop\">\n        <keyword context=\"#pop!NoRegExp\" attribute=\"Module Function (Built-in)\" String=\"math_functions\" />\n        <IncludeRules context=\"DefaultMemberObject\" />\n      </context>\n\n      <context name=\"Number\" attribute=\"Normal Text\" lineEndContext=\"#pop\">\n        <!-- fast path -->\n        <RegExpr String=\"&beforeDigit;[0-9]|&beforePointFloat;\\.[0-9]\" context=\"AssumeNumber\" lookAhead=\"1\"/>\n      </context>\n      <context name=\"AssumeNumber\" attribute=\"Normal Text\" lineEndContext=\"#pop\">\n        <!-- Hexadecimal: 0xA1, Binary: 0b01, Octal: 0o71 -->\n        <RegExpr attribute=\"Hex\" String=\"0x(?:_?[0-9a-fA-F])+\" context=\"CheckSuffixError\"/>\n        <RegExpr attribute=\"Binary\" String=\"0b(?:_?[01])+\" context=\"CheckSuffixError\"/>\n        <RegExpr attribute=\"Octal\" String=\"0o(?:_?[0-7])+\" context=\"CheckSuffixError\"/>\n        <!-- Float: 1.1 ; 1. ; .1 ; 1e3 ; 1.1e3 ; 1.e3 ; .1e3 -->\n        <RegExpr attribute=\"Float\" String=\"(?:&digitPart;(?:\\.(?:&digitPart;)?)?|\\.&digitPart;)[eE][\\+\\-]?&digitPart;|&digitPart;\\.(?:&digitPart;)?|\\.&digitPart;\" context=\"CheckSuffixError\"/>\n        <!-- Decimal: 123 ; 000 -->\n        <!-- l and L are python2 suffixes -->\n        <RegExpr attribute=\"Int\" String=\"(?:[1-9](?:_?[0-9])*|0(?:_?0)*)\" context=\"CheckSuffixError\"/>\n      </context>\n\n      <context name=\"CheckSuffixError\" attribute=\"Normal Text\" lineEndContext=\"#pop#pop\" fallthrough=\"1\" fallthroughContext=\"#pop#pop\">\n        <RegExpr attribute=\"Error\" String=\"\\w+\" context=\"#pop#pop\"/>\n      </context>\n\n      <context name=\"StringVariants\" attribute=\"Normal Text\" lineEndContext=\"#stay\">\n        <!-- fast path -->\n        <RegExpr String=\"(?:f|r|s)?['&quot;]\" insensitive=\"true\" context=\"AssumeStringVariants\" lookAhead=\"1\"/>\n      </context>\n\n      <context name=\"AssumeStringVariants\" attribute=\"Normal Text\" lineEndContext=\"#stay\">\n        <StringDetect attribute=\"String\" String=\"'''\"                context=\"#pop!Triple A-string\" beginRegion=\"Triple A-region\"/>\n        <StringDetect attribute=\"String\" String=\"&quot;&quot;&quot;\" context=\"#pop!Triple Q-string\" beginRegion=\"Triple Q-region\"/>\n        <StringDetect attribute=\"String\" String=\"'\"                  context=\"#pop!Single A-string\"/>\n        <StringDetect attribute=\"String\" String=\"&quot;\"             context=\"#pop!Single Q-string\"/>\n\n        <StringDetect attribute=\"F-String\" String=\"f'''\"                insensitive=\"true\" context=\"#pop!Triple A-F-String\" beginRegion=\"Triple A-region\"/>\n        <StringDetect attribute=\"F-String\" String=\"f&quot;&quot;&quot;\" insensitive=\"true\" context=\"#pop!Triple Q-F-String\" beginRegion=\"Triple Q-region\"/>\n        <StringDetect attribute=\"F-String\" String=\"f'\"                  insensitive=\"true\" context=\"#pop!Single A-F-String\"/>\n        <StringDetect attribute=\"F-String\" String=\"f&quot;\"             insensitive=\"true\" context=\"#pop!Single Q-F-String\"/>\n\n        <StringDetect attribute=\"R-String\" String=\"r'''\"                insensitive=\"true\" context=\"#pop!Triple A-R-String\" beginRegion=\"Triple A-region\"/>\n        <StringDetect attribute=\"R-String\" String=\"r&quot;&quot;&quot;\" insensitive=\"true\" context=\"#pop!Triple Q-R-String\" beginRegion=\"Triple Q-region\"/>\n        <StringDetect attribute=\"R-String\" String=\"r'\"                  insensitive=\"true\" context=\"#pop!Single A-R-String\"/>\n        <StringDetect attribute=\"R-String\" String=\"r&quot;\"             insensitive=\"true\" context=\"#pop!Single Q-R-String\"/>\n\n        <StringDetect attribute=\"S-String\" String=\"s'''\"                insensitive=\"true\" context=\"#pop!Triple A-S-String\" beginRegion=\"Triple A-region\"/>\n        <StringDetect attribute=\"S-String\" String=\"s&quot;&quot;&quot;\" insensitive=\"true\" context=\"#pop!Triple Q-S-String\" beginRegion=\"Triple Q-region\"/>\n        <StringDetect attribute=\"S-String\" String=\"s'\"                  insensitive=\"true\" context=\"#pop!Single A-S-String\"/>\n        <StringDetect attribute=\"S-String\" String=\"s&quot;\"             insensitive=\"true\" context=\"#pop!Single Q-S-String\"/>\n      </context>\n\n      <!-- Comments -->\n\n      <context name=\"Hash comment\" attribute=\"Comment\" lineEndContext=\"#pop\">\n        <DetectSpaces />\n        <IncludeRules context=\"##Comments\" />\n        <DetectIdentifier/>\n      </context>\n\n      <!-- escape characters -->\n      <context name=\"stringescape\" attribute=\"String Char\" lineEndContext=\"#stay\">\n        <DetectChar char=\"\\\" lookAhead=\"1\" context=\"stringescape2\"/>\n      </context>\n      <context name=\"stringescape2\" attribute=\"String Char\" lineEndContext=\"#pop\">\n        <RegExpr attribute=\"String Char\" String=\"\\\\[\\\\'&quot;bfnrt]|\\\\[0-7]{1,3}|&escapedHexUni;\" context=\"#pop\"/>\n        <LineContinue attribute=\"Operator\" context=\"#pop\"/>\n        <RegExpr attribute=\"Error\" String=\".\" context=\"#pop\"/>\n      </context>\n\n      <!-- f-literals -->\n      <context name=\"stringinterpolation\" attribute=\"F-String\" lineEndContext=\"#stay\">\n        <Detect2Chars attribute=\"String Char\" char=\"{\" char1=\"{\" context=\"#stay\"/>\n        <DetectChar attribute=\"String Substitution\" char=\"{\" context=\"String Interpolation\"/>\n        <Detect2Chars attribute=\"String Char\" char=\"}\" char1=\"}\" context=\"#stay\"/>\n        <DetectChar attribute=\"Error\" char=\"}\" context=\"#stay\"/>\n      </context>\n      <context name=\"String Interpolation\" attribute=\"String Substitution\" lineEndContext=\"#stay\">\n        <RegExpr attribute=\"String Substitution\" String=\".*\\}\" context=\"#pop\"/>\n        <IncludeRules context=\"Normal\"/>\n      </context>\n\n      <!-- Triple-quoted A-strings -->\n      <context name=\"Triple A-string\" attribute=\"String\" lineEndContext=\"#stay\" noIndentationBasedFolding=\"true\">\n        <DetectSpaces attribute=\"String\"/>\n        <DetectIdentifier attribute=\"String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringformat\"/>\n        <StringDetect attribute=\"String\" String=\"'''\" context=\"#pop\" endRegion=\"Triple A-region\"/>\n      </context>\n\n      <context name=\"Triple A-F-String\" attribute=\"F-String\" lineEndContext=\"#stay\" noIndentationBasedFolding=\"true\">\n        <DetectSpaces attribute=\"F-String\"/>\n        <DetectIdentifier attribute=\"F-String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringinterpolation\"/>\n        <StringDetect attribute=\"F-String\" String=\"'''\" context=\"#pop\" endRegion=\"Triple A-region\"/>\n      </context>\n\n      <context name=\"Triple A-R-String\" attribute=\"R-String\" lineEndContext=\"#stay\" noIndentationBasedFolding=\"true\">\n        <DetectSpaces attribute=\"R-String\"/>\n        <DetectIdentifier attribute=\"R-String\"/>\n        <Detect2Chars attribute=\"R-String\" char=\"\\\" char1=\"'\"/>\n        <Detect2Chars attribute=\"R-String\" char=\"\\\" char1=\"\\\"/>\n        <IncludeRules context=\"stringformat\"/>\n        <StringDetect attribute=\"R-String\" String=\"'''\" context=\"#pop\" endRegion=\"Triple A-region\"/>\n      </context>\n\n      <context name=\"Triple A-S-String\" attribute=\"S-String\" lineEndContext=\"#stay\" noIndentationBasedFolding=\"true\">\n        <DetectSpaces attribute=\"S-String\"/>\n        <DetectIdentifier attribute=\"S-String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringinterpolation\"/>\n        <StringDetect attribute=\"S-String\" String=\"'''\" context=\"#pop\" endRegion=\"Triple A-region\"/>\n      </context>\n\n      <!-- Triple-quoted Q-strings -->\n      <context name=\"Triple Q-string\" attribute=\"String\" lineEndContext=\"#stay\" noIndentationBasedFolding=\"true\">\n        <DetectSpaces attribute=\"String\"/>\n        <DetectIdentifier attribute=\"String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringformat\"/>\n        <StringDetect attribute=\"String\" String=\"&quot;&quot;&quot;\" context=\"#pop\" endRegion=\"Triple Q-region\"/>\n      </context>\n\n      <context name=\"Triple Q-F-String\" attribute=\"F-String\" lineEndContext=\"#stay\" noIndentationBasedFolding=\"true\">\n        <DetectSpaces attribute=\"F-String\"/>\n        <DetectIdentifier attribute=\"F-String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringinterpolation\"/>\n        <StringDetect attribute=\"F-String\" String=\"&quot;&quot;&quot;\" context=\"#pop\" endRegion=\"Triple Q-region\"/>\n      </context>\n\n      <context name=\"Triple Q-R-String\" attribute=\"R-String\" lineEndContext=\"#stay\" noIndentationBasedFolding=\"true\">\n        <DetectSpaces attribute=\"R-String\"/>\n        <DetectIdentifier attribute=\"R-String\"/>\n        <Detect2Chars attribute=\"R-String\" char=\"\\\" char1=\"&quot;\"/>\n        <Detect2Chars attribute=\"R-String\" char=\"\\\" char1=\"\\\"/>\n        <IncludeRules context=\"stringformat\"/>\n        <StringDetect attribute=\"R-String\" String=\"&quot;&quot;&quot;\" context=\"#pop\" endRegion=\"Triple Q-region\"/>\n      </context>\n\n      <context name=\"Triple Q-S-String\" attribute=\"S-String\" lineEndContext=\"#stay\" noIndentationBasedFolding=\"true\">\n        <DetectSpaces attribute=\"S-String\"/>\n        <DetectIdentifier attribute=\"S-String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringinterpolation\"/>\n        <StringDetect attribute=\"S-String\" String=\"&quot;&quot;&quot;\" context=\"#pop\" endRegion=\"Triple Q-region\"/>\n      </context>\n\n      <!-- Single-quoted A-strings -->\n      <context name=\"Single A-string\" attribute=\"String\" lineEndContext=\"#pop!UnfinishedStringError\">\n        <DetectSpaces attribute=\"String\"/>\n        <DetectIdentifier attribute=\"String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringformat\"/>\n        <DetectChar attribute=\"String\" char=\"'\" context=\"#pop\"/>\n      </context>\n\n      <context name=\"Single A-F-String\" attribute=\"F-String\" lineEndContext=\"#pop!UnfinishedStringError\">\n        <DetectSpaces attribute=\"F-String\"/>\n        <DetectIdentifier attribute=\"F-String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringinterpolation\"/>\n        <DetectChar attribute=\"F-String\" char=\"'\" context=\"#pop\"/>\n      </context>\n\n      <context name=\"Single A-R-String\" attribute=\"R-String\" lineEndContext=\"#pop!UnfinishedStringError\">\n        <DetectSpaces attribute=\"R-String\"/>\n        <DetectIdentifier attribute=\"R-String\"/>\n        <Detect2Chars attribute=\"R-String\" char=\"\\\" char1=\"'\"/>\n        <Detect2Chars attribute=\"R-String\" char=\"\\\" char1=\"\\\"/>\n        <IncludeRules context=\"stringformat\"/>\n        <DetectChar attribute=\"R-String\" char=\"'\" context=\"#pop\"/>\n        <LineContinue attribute=\"R-String\" context=\"#stay\"/>\n      </context>\n\n      <context name=\"Single A-S-String\" attribute=\"S-String\" lineEndContext=\"#pop!UnfinishedStringError\">\n        <DetectSpaces attribute=\"S-String\"/>\n        <DetectIdentifier attribute=\"S-String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringinterpolation\"/>\n        <DetectChar attribute=\"S-String\" char=\"'\" context=\"#pop\"/>\n      </context>\n\n      <!-- Single-quoted Q-strings -->\n      <context name=\"Single Q-string\" attribute=\"String\" lineEndContext=\"#pop!UnfinishedStringError\">\n        <DetectSpaces attribute=\"String\"/>\n        <DetectIdentifier attribute=\"String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringformat\"/>\n        <DetectChar attribute=\"String\" char=\"&quot;\" context=\"#pop\"/>\n      </context>\n\n      <context name=\"Single Q-F-String\" attribute=\"F-String\" lineEndContext=\"#pop!UnfinishedStringError\">\n        <DetectSpaces attribute=\"F-String\"/>\n        <DetectIdentifier attribute=\"F-String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringinterpolation\"/>\n        <DetectChar attribute=\"F-String\" char=\"&quot;\" context=\"#pop\"/>\n      </context>\n\n      <context name=\"Single Q-R-String\" attribute=\"R-String\" lineEndContext=\"#pop!UnfinishedStringError\">\n        <DetectSpaces attribute=\"R-String\"/>\n        <DetectIdentifier attribute=\"R-String\"/>\n        <Detect2Chars attribute=\"R-String\" char=\"\\\" char1=\"&quot;\"/>\n        <Detect2Chars attribute=\"R-String\" char=\"\\\" char1=\"\\\"/>\n        <IncludeRules context=\"stringformat\"/>\n        <DetectChar attribute=\"R-String\" char=\"&quot;\" context=\"#pop\"/>\n        <LineContinue attribute=\"R-String\" context=\"#stay\"/>\n      </context>\n\n      <context name=\"Single Q-S-String\" attribute=\"S-String\" lineEndContext=\"#pop!UnfinishedStringError\">\n        <DetectSpaces attribute=\"S-String\"/>\n        <DetectIdentifier attribute=\"S-String\"/>\n        <IncludeRules context=\"stringescape\"/>\n        <IncludeRules context=\"stringinterpolation\"/>\n        <DetectChar attribute=\"S-String\" char=\"&quot;\" context=\"#pop\"/>\n      </context>\n    </contexts>\n\n    <itemDatas>\n      <itemData name=\"Normal Text\"      defStyleNum=\"dsNormal\"   spellChecking=\"false\" />\n\n      <itemData name=\"Keyword\"          defStyleNum=\"dsKeyword\"  spellChecking=\"false\" />\n\n      <itemData name=\"Import\"           defStyleNum=\"dsImport\"   spellChecking=\"false\" />\n      <itemData name=\"Operator\"         defStyleNum=\"dsOperator\" spellChecking=\"false\" />\n      <itemData name=\"Data Type\"        defStyleNum=\"dsDataType\" spellChecking=\"false\" />\n      <itemData name=\"Builtin Function\" defStyleNum=\"dsBuiltIn\"  spellChecking=\"false\" />\n      <itemData name=\"Special Variable\" defStyleNum=\"dsConstant\" spellChecking=\"false\" />\n\n      <itemData name=\"Float\" defStyleNum=\"dsFloat\" spellChecking=\"false\"/>\n      <itemData name=\"Int\" defStyleNum=\"dsDecVal\" spellChecking=\"false\"/>\n      <itemData name=\"Hex\" defStyleNum=\"dsBaseN\" spellChecking=\"false\"/>\n      <itemData name=\"Octal\" defStyleNum=\"dsBaseN\" spellChecking=\"false\"/>\n      <itemData name=\"Binary\" defStyleNum=\"dsBaseN\" spellChecking=\"false\"/>\n      <itemData name=\"Comment\" defStyleNum=\"dsComment\"/>\n      <itemData name=\"String\" defStyleNum=\"dsString\"/>\n      <itemData name=\"F-String\" defStyleNum=\"dsSpecialString\"/>\n      <itemData name=\"R-String\" defStyleNum=\"dsVerbatimString\"/>\n      <itemData name=\"S-String\" defStyleNum=\"dsVerbatimString\"/>\n      <itemData name=\"String Char\" defStyleNum=\"dsChar\" spellChecking=\"false\"/>\n      <itemData name=\"String Substitution\" defStyleNum=\"dsSpecialChar\" spellChecking=\"false\"/>\n      <itemData name=\"Annotation\" defStyleNum=\"dsAttribute\" spellChecking=\"false\"/>\n      <itemData name=\"Error\" defStyleNum=\"dsError\"/>\n    </itemDatas>\n  </highlighting>\n\n  <general>\n    <folding indentationsensitive=\"1\" />\n    <comments>\n      <comment name=\"singleLine\" start=\"#\" position=\"afterwhitespace\" />\n    </comments>\n    <keywords casesensitive=\"1\" />\n  </general>\n\n</language>\n<!-- kate: indent-width 2; tab-width 2; -->\n"
  },
  {
    "path": "grammars/README.md",
    "content": "# Grammars / syntax highlighting\n\nPRQL contains multiple grammar definitions to enable tools to highlight PRQL\ncode. These are all intended to provide as good an experience as the grammar\nsupports. Please raise any shortcomings in a GitHub issue.\n\nThe definitions are somewhat scattered around the codebase; this page serves as\nan index.\n\n- [Ace](https://ace.c9.io/) — supported. The grammar is upstream\n  ([prql_highlight_rules.js](https://github.com/ajaxorg/ace/blob/master/src/mode/prql_highlight_rules.js)).\n  See the [demo](https://prql-lang.org/demos/ace-demo).\n\n- [chroma](https://github.com/alecthomas/chroma) — Go library used by the static\n  website generator Hugo. The grammar is upstream\n  ([prql.xml](https://github.com/alecthomas/chroma/blob/master/lexers/embedded/prql.xml)).\n  See the [demo](https://swapoff.org/chroma/playground/).\n\n- [CotEditor](https://coteditor.com/) — text editor for macOS. File is at\n  [`grammars/CotEditor/`](https://github.com/PRQL/prql/tree/main/grammars/CotEditor/).\n\n- [Lezer](https://lezer.codemirror.net/) — used by CodeMirror editors. The PRQL\n  file is at\n  [`grammars/prql-lezer/README.md`](https://github.com/PRQL/prql/tree/main/grammars/prql-lezer/README.md).\n\n- emacs — used by terminal-based text editor GNU Emacs. File is at\n  [`grammars/emacs/`](https://github.com/PRQL/prql/tree/main/grammars/emacs/).\n\n- GtkSourceView — used by GNOME Text Editor, GNOME Builder and other GTK\n  applications. File is at\n  [`grammars/GtkSourceView/`](https://github.com/PRQL/prql/tree/main/grammars/GtkSourceView/).\n\n- [Handlebars](https://handlebarsjs.com/) — currently duplicated:\n  - The book:\n    [`book/highlight-prql.js`](https://github.com/PRQL/prql/blob/main/web/book/highlight-prql.js)\n  - The website (outside of the book & playground):\n    [`website/themes/prql-theme/static/plugins/highlight/prql.js`](https://github.com/PRQL/prql/blob/main/web/book/highlight-prql.js)\n\n- [Helix](https://helix-editor.com/) — supported. The grammar is\n  [upstream](https://github.com/helix-editor/helix/tree/master/runtime/queries/prql).\n\n- [Kakoune](https://kakoune.org/) — supported. The grammar is\n  [upstream](https://github.com/mawww/kakoune/blob/master/rc/filetype/prql.kak).\n\n- KSyntaxHighlighting — used by Kate, KWrite and KDevelop and other Qt\n  applications. File is at\n  [`grammars/KSyntaxHighlighting/`](https://github.com/PRQL/prql/tree/main/grammars/KSyntaxHighlighting/).\n\n- [micro](https://micro-editor.github.io/) — used by terminal-based text editor\n  Micro. The grammar is\n  [upstream](https://github.com/zyedidia/micro/blob/master/runtime/syntax/prql.yaml).\n\n- [nano](https://nano-editor.org/) — used by terminal-based text editor GNU\n  nano. File is at\n  [`grammars/nano/`](https://github.com/PRQL/prql/tree/main/grammars/nano/).\n\n- Sublime Text — in the [`sublime-prql`](https://github.com/PRQL/sublime-prql/)\n  repository.\n\n- TextMate — used by the VS Code extension; in the `prql-vscode` repo in\n  [`prql-vscode/syntaxes/prql.tmLanguage.json`](https://github.com/PRQL/prql-vscode/blob/main/syntaxes/prql.tmLanguage.json).\n\n- [Monarch](https://microsoft.github.io/monaco-editor/monarch.html) — used by\n  the Monaco editor, which we use for the Playground. The grammar is at\n  [`playground/src/workbench/prql-syntax.js`](https://github.com/PRQL/prql/blob/main/web/playground/src/workbench/prql-syntax.js).\n\n- [Pygments](https://pygments.org/) — Python library used by Wikipedia,\n  Bitbucket, Sphinx and [more](https://pygments.org/faq/#who-uses-pygments). The\n  grammar is upstream\n  ([prql.py](https://github.com/pygments/pygments/blob/master/pygments/lexers/prql.py)).\n  See the [demo](https://pygments.org/demo/).\n\n- [Raku](https://raku.org/) — Grammar can be found at\n  [`grammars/raku/`](https://github.com/PRQL/prql/tree/main/grammars/raku/).\n\n- [TEA](https://github.com/psemiletov/tea-qt/) — supported. The grammar is\n  [upstream](https://github.com/psemiletov/tea-qt/blob/master/hls/prql.xml).\n\n- [Tree-Sitter](https://tree-sitter.github.io/tree-sitter) — used by the neovim\n  and helix. The grammar can be found at\n  [https://github.com/PRQL/tree-sitter-prql](https://github.com/PRQL/tree-sitter-prql).\n\n- [vim](https://www.vim.org/) — used by terminal-based text editor Vim.\n  Instructions at\n  [`grammars/vim/`](https://github.com/PRQL/prql/tree/main/grammars/vim/). The\n  grammar is\n  [upstream](https://github.com/vim/vim/blob/master/runtime/syntax/prql.vim).\n  - [Neovim](https://neovim.io/) supported. Grammar is upstream.\n"
  },
  {
    "path": "grammars/emacs/README.md",
    "content": "# Syntax highlighting for GNU Emacs\n\nThis is a syntax highlighting file for GNU Emacs.\n\n## Installation\n\nCopy the `prql-mode.el` file to:\n\n    ~/.emacs.d/custom-modes/\n\nThen, edit your `~/emacs.d/init.el` file and add the following:\n\n```emacs\n(add-to-list 'load-path \"~/.emacs.d/custom-modes/\")\n(require 'prql-mode)\n\n(add-to-list 'auto-mode-alist '(\"\\\\.prql\\\\'\" . prql-mode))\n```\n"
  },
  {
    "path": "grammars/emacs/prql-mode.el",
    "content": ";;; prql-mode.el --- Major mode for PRQL language -*- lexical-binding: t;\n\n;; URL: https://github.com/PRQL/prql\n;; Keywords: languages\n;; Version: 0.1\n\n;;; Commentary:\n\n;; Provides syntax highlighting for PRQL.\n\n;;; Code:\n\n(defvar prql-mode-syntax-table\n  (let ((table (make-syntax-table)))\n    ;; Define comment syntax\n    (modify-syntax-entry ?# \"<\" table)\n    (modify-syntax-entry ?\\n \">\" table)\n    table)\n  \"Syntax table for `prql-mode'.\")\n\n(defconst prql-constants\n  '(\"true\" \"false\" \"this\" \"that\" \"null\")\n  \"List of PRQL constants.\")\n\n(defconst prql-data-types\n  '(\"bool\" \"float\" \"int\" \"int8\" \"int16\" \"int32\" \"int64\" \"int128\" \"text\" \"date\" \"time\" \"timestamp\")\n  \"List of PRQL data types.\")\n\n(defconst prql-builtin-functions\n  '(\"aggregate\" \"derive\" \"filter\" \"from\" \"group\" \"join\" \"select\" \"sort\" \"take\" \"window\")\n  \"List of PRQL built-in function.\")\n\n(defconst prql-operators\n  '(\"!\" \"=\" \"&&\" \"||\"\n    \"+\" \"-\" \"*\" \"/\" \"%\"\n    \"<\" \">\" \"~=\")\n  \"List of PRQL operators.\")\n\n(defconst prql-numbers\n  '(\"0o[0-7]+\"\n    \"0x[0-9a-fA-F]+\"\n    \"0b[01]+\"\n    \"[0-9]+*\")\n  \"Regex for matching PRQL numbers.\")\n\n(defconst prql-other-keywords\n  '(\"prql\" \"case\" \"let\" \"type\" \"alias\" \"in\" \"loop\" \"module\")\n  \"List of other PRQL keywords.\")\n\n(defvar prql-font-lock-keywords\n  ;; Define keywords and patterns for highlighting\n  `(\n    ,(cons (regexp-opt prql-constants 'words) 'font-lock-constant-face)\n    ,(cons (regexp-opt prql-other-keywords 'words) 'font-lock-keyword-face)\n    ,(cons (regexp-opt prql-builtin-functions 'words) 'font-lock-builtin-face)\n    ,(cons (regexp-opt prql-data-types 'words) 'font-lock-type-face)\n    ,@(mapcar (lambda (kw) (cons kw 'font-lock-builtin-face)) prql-operators)\n    ,@(mapcar (lambda (kw) (cons kw 'font-lock-constant-face)) prql-numbers)))\n\n(define-derived-mode prql-mode prog-mode \"PRQL\"\n  \"Major mode for editing PRQL code.\"\n  ;; Set the syntax table\n  (set-syntax-table prql-mode-syntax-table)\n  ;; Set font lock keywords\n  (setq font-lock-defaults '((prql-font-lock-keywords)))\n  ;; Enable automatic indentation\n  (setq indent-tabs-mode nil)\n  (setq tab-width 2))\n\n;; Add the mode to the auto-mode-alist for .prql files\n(add-to-list 'auto-mode-alist '(\"\\\\.prql\\\\'\" . prql-mode))\n\n(provide 'prql-mode)\n\n;;; prql-mode.el ends here\n"
  },
  {
    "path": "grammars/nano/README.md",
    "content": "# Syntax highlighting for GNU nano\n\nThis is a syntax highlighting file the [GNU nano](https://nano-editor.org/) text\neditor.\n\n## Installation\n\nTo install place the `prql.nanorc` file in the `~/.nano/` directory and and\ninclude the following line in your `.nanorc` file.\n\n    include \"~/.nano/prql.nanorc\"\n\nYou can append it with this command:\n\n    echo 'include \"~/.nano/prql.nanorc\"' >> ~/.nanorc\n"
  },
  {
    "path": "grammars/nano/prql.nanorc",
    "content": "## Syntax highlighting for PRQL.\n\nsyntax python \"\\.prql$\"\nmagic \"PRQL script\"\ncomment \"#\"\n\n# Types.\ncolor green \"\\<(int(8|16|32|64|128)?|float(32|64)|bool|text|date|time|timestamp)\\>\"\n\n# Keywords.\ncolor yellow \"\\<let|module|prql\\>\"\n\n# Transforms.\ncolor brightcyan \"\\<(aggregate|derive|filter|from|group|join|select|sort|take|window)\\>\"\n\n# Special values.\ncolor brightmagenta \"\\<(false|null|true|this|that)\\>\"\n\n# Decorators.\ncolor cyan start=\"@\\{\" end=\"\\}\"\n\n# Mono-quoted strings.\ncolor brightgreen \"[frs]?'([^'\\]|\\\\.)*'|[frs]?\"([^\"\\]|\\\\.)*\"|'''|\"\"\"\"\ncolor normal \"'''|\"\"\"\"\n# Comments.\ncolor gray \"(^|[[:blank:]])#.*\"\n# Triple-quoted strings.\ncolor brightgreen start=\"[frs]?'''([^'),]|$)\" end=\"(^|[^(\\])'''\"\ncolor brightgreen start=\"[frs]?\"\"\"([^\"),]|$)\" end=\"(^|[^(\\])\"\"\"\"\n\n# Backslash escapes.\ncolor lime \"\\\\($|[\\'\"bfnrt]|[0-3]?[0-7]?[0-7]|x[[:xdigit:]]{2})\"\ncolor lime \"\\\\(N\\{[[:alpha:]]+\\}|u\\{[[:xdigit:]]{1,6}\\})\"\n\n# Reminders.\ncolor brightwhite,yellow \"\\<(FIXME|TODO)\\>\"\n\n# Trailing whitespace.\ncolor ,green \"[[:space:]]+$\"\n"
  },
  {
    "path": "grammars/prql-lezer/.gitignore",
    "content": "/node_modules/\n/src/parser.*\n/dist\n"
  },
  {
    "path": "grammars/prql-lezer/README.md",
    "content": "# prql-lezer\n\nA Lezer / CodeMirror grammar for PRQL. It's largely fully-functioning, with a\nfew small TODOs in the [grammar file](src/prql.grammar).\n\nCodeMirror grammars are required by some downstream tools, including\n[Jupyter syntax highlighting](https://github.com/PRQL/pyprql/issues/45). As of\n2022-12 none yet use it.\n\nWe don't yet have the JS machinery around it, and it's not published to any\npackage managers. We can add that shortly. Possibly it'll go into its own repo.\n\n## Developing\n\nGiven there aren't yet tests, we've been developing this by:\n\n- Opening <https://lezer-playground.vercel.app/>\n- Pasting an example query\n- Pasting the current grammar\n- Fixing any issues in the grammar\n- Copying the grammar back into the repo\n\n## Instructions\n\nInstall dependencies:\n\n    npm install\n\nBuild:\n\n    npm run build\n\nTest:\n\n    npm run test\n"
  },
  {
    "path": "grammars/prql-lezer/package.json",
    "content": "{\n  \"name\": \"prql-lezer\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Lezer-based PRQL grammar\",\n  \"main\": \"dist/index.cjs\",\n  \"type\": \"module\",\n  \"exports\": {\n    \"import\": \"./dist/index.js\",\n    \"require\": \"./dist/index.cjs\"\n  },\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"author\": \"PRQL Project\",\n  \"license\": \"MIT\",\n  \"directories\": {\n    \"test\": \"test\"\n  },\n  \"scripts\": {\n    \"build\": \"lezer-generator src/prql.grammar -o src/parser && rollup -c\",\n    \"build-debug\": \"lezer-generator src/prql.grammar --names -o src/parser && rollup -c\",\n    \"prepare\": \"npm run build\",\n    \"test\": \"mocha test/test-*.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PRQL/prql.git\",\n    \"directory\": \"grammars/prql-lezer\"\n  },\n  \"keywords\": [\n    \"lezer\",\n    \"prql\"\n  ],\n  \"bugs\": {\n    \"url\": \"https://github.com/PRQL/prql/issues\"\n  },\n  \"homepage\": \"https://prql-lang.org/\",\n  \"devDependencies\": {\n    \"@lezer/generator\": \"^1.5.1\",\n    \"@rollup/plugin-node-resolve\": \"^15.2.3\",\n    \"mocha\": \"^10.2.0\",\n    \"rollup\": \"^4.5.2\"\n  },\n  \"dependencies\": {\n    \"@lezer/highlight\": \"^1.2.0\",\n    \"@lezer/lr\": \"^1.3.14\"\n  }\n}\n"
  },
  {
    "path": "grammars/prql-lezer/rollup.config.js",
    "content": "import { nodeResolve } from \"@rollup/plugin-node-resolve\";\n\nexport default {\n  input: \"./src/parser.js\",\n  output: [\n    {\n      format: \"cjs\",\n      file: \"./dist/index.cjs\",\n    },\n    {\n      format: \"es\",\n      file: \"./dist/index.js\",\n    },\n  ],\n  external(id) {\n    return !/^[\\.\\/]/.test(id);\n  },\n  plugins: [nodeResolve()],\n};\n"
  },
  {
    "path": "grammars/prql-lezer/src/highlight.js",
    "content": "import { styleTags, tags as t } from \"@lezer/highlight\";\n\nexport const prqlHighlight = styleTags({\n  \"CallExpression/Identifier\": t.function(t.variableName),\n  module: t.moduleKeyword,\n  let: t.definitionKeyword,\n  case: t.controlKeyword,\n  in: t.operatorKeyword,\n  Annotation: t.annotation,\n  Comment: t.lineComment,\n  Docblock: t.docString,\n  \"this that\": t.self,\n  null: t.null,\n  Boolean: t.bool,\n  Integer: t.integer,\n  Float: t.float,\n  DateTime: t.color,\n  DeclarationItem: t.propertyName,\n  TypeName: t.typeName,\n  Escape: t.escape,\n  String: t.string,\n  FString: t.special(t.string),\n  RString: t.special(t.string),\n  SString: t.special(t.string),\n  TimeUnit: t.unit,\n  ArithOp: t.arithmeticOperator,\n  CompareOp: t.compareOperator,\n  LogicOp: t.logicOperator,\n  Equals: t.definitionOperator,\n  Parameter: t.processingInstruction,\n  VariableName: t.variableName,\n  \"( )\": t.paren,\n  \"[ ]\": t.squareBracket,\n  \"{ }\": t.brace,\n  \"| ,\": t.separator,\n});\n"
  },
  {
    "path": "grammars/prql-lezer/src/prql.grammar",
    "content": "// TODO:\n// - Do we want to highlight built-in transforms such as `from` differently to\n//   normal functions?\n// - A few small TODOs included below\n\n@precedence {\n  power @right,\n  prefix,\n  times @left,\n  plus @left,\n  compare @left,\n  and @left,\n  or @left\n}\n\n@top Query { statements }\n\n@skip { space | Comment | Docblock | wrappedLine }\n\nstatements { newline* QueryDefinition? Module? Annotation? VariableDeclaration* pipelineStatement? end? }\n\nQueryDefinition { @specialize<identPart, \"prql\"> NamedArg+ newline+ }\n\nModule { kw<\"module\"> Identifier \"{\" statements \"}\" }\n\npipelineStatement { Pipeline (~ambigNewline newline+ | end)}\n\nPipeline { CallExpression (pipe CallExpression)* | expression \"|\" Identifier }\n\npipe { \"|\" | ~ambigNewline newline+ }\n\nTupleExpression { \"{\" newline* tupleItem ((\",\" newline*) tupleItem)* \",\"? newline* \"}\" }\n\ntupleItem { DeclarationTuple | expression | CallExpression | CaseBranch }\n\nAnnotation { \"@{\" commaSep<Declaration>? \"}\" newline }\n\n// Ideally we would force a space after `Identifier` to prevent an invalid s-string\n// being parsed as a CallExpression, e.g. `s\"{a\"` -> `s` & `\"{a\"'. But we\n// can't seem to force a space because it's in our skip, and I can't see\n// a way of changing the skip expression to only specialize on a single item\nCallExpression { Identifier ArgList { (NamedArg | Declaration | test)+ } }\n\nNamedArg { ArgumentName { identPart } \":\" expression }\nDeclaration { DeclarationItem { identPart } \"=\" expression }\nDeclarationTuple { DeclarationItem { identPart } \"=\" expression }\nCaseBranch { expression \"=>\" expression }\n// Possibly we could only accept case branches inside the TupleExpression?\nCaseExpression { @specialize<identPart, \"case\"> TupleExpression }\n\nNestedPipeline { \"(\" newline* Pipeline ~ambigNewline newline? \")\" }\n\ncommaSep<expr> { newline* expr (\",\" newline* expr newline*)* \",\"? }\nArithOp<expr> { expr }\nCompareOp<expr> { expr }\nLogicOp<expr> { expr }\n\n// The name \"test\" here means equality testing. It is name used in the Python grammar.\ntest { testInner }\n\ntestInner { binaryTest | unaryTest | expression }\n\nbinaryTest[@name=\"BinaryExpression\"] {\n  testInner !or LogicOp<\"||\" | \"??\"> testInner |\n  testInner !and LogicOp<\"&&\"> testInner |\n  testInner !compare (CompareOp<\"==\" | \"!=\" | \"~=\" | \">=\" | \"<=\" | \">\" | \"<\"> | kw<\"in\">) testInner\n}\n\nunaryTest[@name=\"UnaryExpression\"] { kw<\"!\"> testInner }\n\nexpression[@isGroup=Expression] {\n  kw<\"this\"> | kw<\"that\"> |\n  kw<\"null\"> |\n  BinaryExpression |\n  UnaryExpression |\n  ArrayExpression |\n  TupleExpression |\n  NestedPipeline |\n  CaseExpression |\n  DateTime |\n  Parameter |\n  ParenthesizedExpression |\n  RangeExpression |\n  Identifier |\n  boolean |\n  number |\n  String | FString | RString | SString |\n  TimeUnit\n}\n\nboolean { @specialize[@name=Boolean]<identPart, \"true\" | \"false\"> }\n\nArrayExpression { \"[\" commaSep<test | \"*\" expression>? \"]\" }\n\nBinaryExpression {\n  expression !plus ArithOp<\"+\" | \"-\"> expression |\n  expression !times ArithOp<\"*\" | \"/\" | \"%\" | \"//\"> expression |\n  expression !power ArithOp{\"**\"} expression\n}\n\nParenthesizedExpression { \"(\" expression \")\" }\n\nUnaryExpression {\n  !prefix ArithOp<\"+\" | \"-\"> expression |\n  !prefix CompareOp<\"==\"> Identifier\n}\n\n// Because this is outside tokens, we can't disallow whitespace.\n// It's outside tokens because otherwise it conflicts with Identifier\nIdentifier { identPart (\".\" (identPart | \"*\"))* }\nVariableName { identPart }\n\nnumber { Integer | Float }\n\nkw<term> { @specialize[@name={term}]<identPart, term> }\n\nVariableDeclaration { kw<\"let\"> VariableName \"=\" (NestedPipeline (newline+ | end) | Lambda) }\n\nLambda { LambdaParam* \"->\" expression (newline+ | end) }\nTypeDefinition { \"<\" TypeName (\"|\" TypeName)* \">\" }\nTypeName { identPart TypeDefinition? }\nLambdaParam { identPart TypeDefinition? (\":\" expression)? }\n\n@skip {} {\n  // Couldn't manage to do these & the interpolated as a template.\n  String {\n    '\"\"\"' (stringContentDouble | Escape)* '\"\"\"' |\n    \"'''\" (stringContentSingle | Escape)* \"'''\" |\n    '\"' (stringContentDouble | Escape)* '\"' |\n    \"'\" (stringContentSingle | Escape)* \"'\"\n  }\n}\n\n@tokens {\n  date { @digit+ \"-\" @digit+ \"-\" @digit+ }\n  time { @digit+ \":\" @digit+ (\":\" @digit+ (\".\" @digit+)?)? }\n  // We can't seem to set the number of digits, so this will allow any\n  // combination of digits & hyphens.\n  DateTime { \"@\" (date | time | date \"T\" time (\"Z\" | (\"-\" | \"+\") @digit+ \":\" @digit+)?) }\n  TimeUnit { @digit+ (\"years\" | \"months\" | \"weeks\" | \"days\" | \"hours\" | \"minutes\" | \"seconds\" | \"milliseconds\" | \"microseconds\") }\n  identifierChar { @asciiLetter | $[_\\u{a1}-\\u{10ffff}] }\n  identPart { identifierChar (identifierChar | \"_\" | @digit)* }\n\n  hex { @digit | $[a-fA-F] }\n\n  Integer {\n    @digit (@digit | \"_\")* (\"e\" (\"+\" | \"-\")? Integer)? |\n    \"0x\" (hex | \"_\")+ |\n    \"0b\" $[01_]+ |\n    \"0o\" $[0-7_]+\n  }\n\n  Float { @digit (@digit | \"_\")* \".\" @digit (@digit | \"_\")* (\"e\" Integer)? }\n  // TODO: This is not as precise as PRQL, which doesn't allow trailing\n  // underscores and allows no digit before the decimal point.\n  space { $[ \\t] }\n\n  Escape {\n    \"\\\\\" (\"x\" hex hex | \"u\" \"{\" hex+ \"}\" | $[bfnrt])\n  }\n\n  Parameter { \"$\" (@digit+ | identPart) }\n\n  stringContentSingle { ![\\\\']+ }\n\n  stringContentDouble { ![\\\\\"]+ }\n\n  Docblock { \"#!\" ![\\n]* }\n  Comment { \"#\" ![\\n]* }\n  @precedence { Docblock, Comment }\n\n  end { @eof }\n  lineWrap { \"\\\\\" }\n  wrappedLine { newline+ (Comment newline+)* lineWrap }\n  newline { \"\\n\" }\n\n  // TODO: Because this can also be used to compile to BETWEEN, ranges should\n  // allow any literal, and arguably any expression.\n  RangeExpression { @digit+ \"..\" @digit+ }\n\n  // TODO: not getting the interpolations highlighted; it just shows the whole\n  // string as a string, because these are all within the `@tokens` block. But\n  // they need to be within this block, because it's not possible to have\n  // negations (e.g. `![{'}` outside it.\n  stringInterpolatedSingle { $['] (![{'] | interpolationInnerSingle)* $['] }\n  stringInterpolatedDouble { $[\"] (![{\"] | interpolationInnerDouble)* $[\"] }\n\n  interpolationInnerSingle { \"{\" ![}']* \"}\" }\n  interpolationInnerDouble { \"{\" ![}\"]* \"}\" }\n\n  interpolatedString<prefix> { prefix (stringInterpolatedDouble | stringInterpolatedSingle) }\n\n  \"=\"[@name=Equals]\n\n  FString { interpolatedString<'f'> }\n  RString { interpolatedString<'r'> }\n  SString { interpolatedString<'s'> }\n\n  // We need to give precedence to `Op_bin` so we don't get `x+y` as `x` & `+y`.\n  // R, S & F strings have precedence over idents beginning with r / s / f (we could\n  // use specialize but I think means we need to redefine `String` for each)\n  @precedence { RangeExpression, Float, TimeUnit, Integer }\n  @precedence { FString, RString, SString, identPart }\n}\n\n@external propSource prqlHighlight from \"./highlight\"\n\n@detectDelim\n"
  },
  {
    "path": "grammars/prql-lezer/test/arithmetics.txt",
    "content": "# Plus\n\nfilter 10 + 10.5\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Integer,ArithOp,Float)))))\n\n# Minus\n\nfilter 10 - 10\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Integer,ArithOp,Integer)))))\n\n# Multiply\n\nfilter 10 * 10\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Integer,ArithOp,Integer)))))\n\n# Divide\n\nfilter 10 / 10\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Integer,ArithOp,Integer)))))\n\n# Exponentiation\n\nfilter 10 ** 10\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Integer,ArithOp,Integer)))))\n\n# Multiple ops\n\nfilter 10 + 10 + 10\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(BinaryExpression(Integer,ArithOp,Integer),ArithOp,Integer)))))\n"
  },
  {
    "path": "grammars/prql-lezer/test/arrays.txt",
    "content": "# Array on one line\n\nfilter [foo, bar, baz]\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(ArrayExpression(Identifier,Identifier,Identifier)))))\n\n# Array on multiple lines\n\nfilter [\n  foo,\n  bar,\n  baz\n]\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(ArrayExpression(Identifier,Identifier,Identifier)))))\n\n# Array on multiple lines with blank lines\n\nfilter [\n\n  foo,\n\n  bar,\n\n  baz\n\n]\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(ArrayExpression(Identifier,Identifier,Identifier)))))\n\n# Array of integers\n\nfilter [1, 2, 3]\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(ArrayExpression(Integer,Integer,Integer)))))\n\n# Array of floats\n\nfilter [1.1, 2.2, 3.3]\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(ArrayExpression(Float,Float,Float)))))\n\n# Array of strings\n\nfilter [\"string\", f\"format\", r\"raw\", s\"server\"]\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(ArrayExpression(String,FString,RString,SString)))))\n"
  },
  {
    "path": "grammars/prql-lezer/test/datetime.txt",
    "content": "# Date YYYY-MM-DD\n\nfilter @1970-01-01\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(DateTime))))\n\n# Time HH:MM\n\nfilter @08:30\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(DateTime))))\n\n# Time HH:MM:SS\n\nfilter @12:00:00\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(DateTime))))\n\n# Time HH:MM:SS.xxx\n\nfilter @12:00:00.500\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(DateTime))))\n\n# Date and time\n\nfilter @1970-01-01T12:00:00\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(DateTime))))\n\n# Date and time with timezone\n\nfilter @1970-01-01T12:00:00+01:00\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(DateTime))))\n\n# Date and time in UTC\n\nfilter @1970-01-01T12:00:00Z\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(DateTime))))\n"
  },
  {
    "path": "grammars/prql-lezer/test/full_queries.txt",
    "content": "# Website showcasing example\n\nfrom invoices\nfilter invoice_date >= @1970-01-16\nderive {\n  transaction_fees = 0.8,\n  income = total - transaction_fees\n}\nfilter income > 1\ngroup customer_id (\n  aggregate {\n    average total\n  }\n)\nsort {-sum_income}\ntake 10\njoin c=customers (==customer_id)\nderive name = f\"{c.last_name}, {c.first_name}\"\nselect {\n  c.customer_id, name, sum_income\n}\nderive db_version = s\"version()\"\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Identifier)),CallExpression(Identifier,ArgList(BinaryExpression(Identifier,CompareOp,DateTime))),CallExpression(Identifier,ArgList(TupleExpression(DeclarationTuple(DeclarationItem,Equals,Float),DeclarationTuple(DeclarationItem,Equals,BinaryExpression(Identifier,ArithOp,Identifier))))),CallExpression(Identifier,ArgList(BinaryExpression(Identifier,CompareOp,Integer))),CallExpression(Identifier,ArgList(Identifier,NestedPipeline(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(CallExpression(Identifier,ArgList(Identifier))))))))),CallExpression(Identifier,ArgList(TupleExpression(UnaryExpression(ArithOp,Identifier)))),CallExpression(Identifier,ArgList(Integer)),CallExpression(Identifier,ArgList(Declaration(DeclarationItem,Equals,Identifier),ParenthesizedExpression(UnaryExpression(CompareOp,Identifier)))),CallExpression(Identifier,ArgList(Declaration(DeclarationItem,Equals,FString))),CallExpression(Identifier,ArgList(TupleExpression(Identifier,Identifier,Identifier))),CallExpression(Identifier,ArgList(Declaration(DeclarationItem,Equals,SString)))))\n"
  },
  {
    "path": "grammars/prql-lezer/test/identifiers.txt",
    "content": "# Basic identifier\n\nfilter foo\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Identifier))))\n\n\n# Identifier with underscore and digit\n\nfilter foo_123\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Identifier))))\n\n# Unicode identifier\n\nfilter räksmörgås\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Identifier))))\n"
  },
  {
    "path": "grammars/prql-lezer/test/misc.txt",
    "content": "# Boolean: true\n\nfilter true\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Boolean))))\n\n# Boolean: false\n\nfilter false\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Boolean))))\n\n# Null\n\nfilter null\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(null))))\n\n# Keyword: this\n\nfilter this\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(this))))\n\n# Keyword: that\n\nfilter that\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(that))))\n\n# Annotation\n\n@{binding_strength=1}\nlet x = y -> z\n\n==>\n\nQuery(Annotation(Declaration(DeclarationItem,Equals,Integer)),VariableDeclaration(let,VariableName,Equals,Lambda(LambdaParam,Identifier)))\n\n# Range: 10..20\n\nfilter 10..20\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(RangeExpression))))\n\n# Comment\n\nfilter 1 # Hello\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Integer))),Comment)\n\n# Two comments\n\n# Hello\n# Bar\n\n==>\n\nQuery(Comment,Comment)\n\n# Comment between pipes\n\nfrom foo\n# filter...\nselect bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Identifier)),Comment,CallExpression(Identifier,ArgList(Identifier))))\n\n# Docblock\n\n#! Hello\nfilter 1\n\n==>\n\nQuery(Docblock,Pipeline(CallExpression(Identifier,ArgList(Integer))))\n\n# Variable declaration\n\nlet foo = (from foo)\n\n==>\n\nQuery(VariableDeclaration(let,VariableName,Equals,NestedPipeline(Pipeline(CallExpression(Identifier,ArgList(Identifier))))))\n\n# Function declaration\n\nlet my_func = arg1 -> arg1\n\n==>\n\nQuery(VariableDeclaration(let,VariableName,Equals,Lambda(LambdaParam,Identifier)))\n\n# Function declaration with two args\n\nlet my_func = arg1 arg2 -> arg1 + arg2\n\n==>\n\nQuery(VariableDeclaration(let,VariableName,Equals,Lambda(LambdaParam,LambdaParam,BinaryExpression(Identifier,ArithOp,Identifier))))\n\n# Function declaration with type annotation\n\nlet my_func = arg1<int32> -> arg1\n\n==>\n\nQuery(VariableDeclaration(let,VariableName,Equals,Lambda(LambdaParam(TypeDefinition(TypeName)),Identifier)))\n\n# Parameter\n\nfilter id == $1\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Identifier,CompareOp,Parameter)))))\n\n# Parenthesized expression\n\nfilter (1)\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(ParenthesizedExpression(Integer)))))\n\n# Simple pipeline\n\nfrom foo | select bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Identifier)),CallExpression(Identifier,ArgList(Identifier))))\n\n# Derive\n\nderive {\n  transaction_fees = 0.8,\n  income = total - transaction_fees\n}\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(DeclarationTuple(DeclarationItem,Equals,Float),DeclarationTuple(DeclarationItem,Equals,BinaryExpression(Identifier,ArithOp,Identifier)))))))\n\n# Nested pipeline\n\ngroup customer_id (\n  aggregate {\n    average total\n  }\n)\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Identifier,NestedPipeline(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(CallExpression(Identifier,ArgList(Identifier)))))))))))\n\n# Tabs as spaces\n\nlet\t\tfoo\t\t=\t\t(foo 1)\n\n==>\n\nQuery(VariableDeclaration(let,VariableName,Equals,NestedPipeline(Pipeline(CallExpression(Identifier,ArgList(Integer))))))\n\n# Module\n\nmodule foo {\n  let x = -> s\"foo()\"\n}\n\n==>\n\nQuery(Module(module,Identifier,VariableDeclaration(let,VariableName,Equals,Lambda(SString))))\n\n# Pipeline within tuple\n\nderive {\n  a = (b | math.abs)\n}\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(DeclarationTuple(DeclarationItem,Equals,NestedPipeline(Pipeline(Identifier,Identifier))))))))\n"
  },
  {
    "path": "grammars/prql-lezer/test/numbers.txt",
    "content": "# Integer\n\nfilter 123\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Integer))))\n\n# Integer with underscore\n\nfilter 123_456\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Integer))))\n\n# Integer with underscores\n\nfilter 123_456_789\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Integer))))\n\n# Decimal\n\nfilter 123.45\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Float))))\n\n# Scientific notation\n\nfilter 123e10\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Integer))))\n\n# Number with time unit\n\nfilter 5years\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(TimeUnit))))\n\n# Binary notation\n\nfilter 0b1111\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Integer))))\n\n# Hex notation\n\nfilter 0xff\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Integer))))\n\n# Octal notation\n\nfilter 0o777\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Integer))))\n"
  },
  {
    "path": "grammars/prql-lezer/test/operators.txt",
    "content": "# == Equals\n\nfilter foo == bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Identifier,CompareOp,Identifier)))))\n\n# != Not equals\n\nfilter foo != bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Identifier,CompareOp,Identifier)))))\n\n# >= Greater than\n\nfilter foo >= bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Identifier,CompareOp,Identifier)))))\n\n# <= Less than\n\nfilter foo <= bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Identifier,CompareOp,Identifier)))))\n\n# ~= Regex match\n\nfilter foo ~= bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Identifier,CompareOp,Identifier)))))\n\n# && And\n\nfilter foo && bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Identifier,LogicOp,Identifier)))))\n\n# || Or\n\nfilter foo || bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Identifier,LogicOp,Identifier)))))\n\n# ?? Coalesce\n\nfilter foo ?? bar\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(BinaryExpression(Identifier,LogicOp,Identifier)))))\n\n# Unary operator -\n\nsort { -name }\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(UnaryExpression(ArithOp,Identifier))))))\n\n# Unary operator ==\n\njoin customers (==customer_id)\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(Identifier,ParenthesizedExpression(UnaryExpression(CompareOp,Identifier))))))\n"
  },
  {
    "path": "grammars/prql-lezer/test/strings.txt",
    "content": "# Single-quoted string\n\nfilter 'Hello'\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(String))))\n\n# Double-quoted string\n\nfilter \"Hello\"\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(String))))\n\n# Single-quoted f-string\n\nfilter f'Hello {name}!'\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(FString))))\n\n# Double-quoted f-string\n\nfilter f\"Hello {name}!\"\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(FString))))\n\n# Single-quoted r-string\n\nfilter r'version()'\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(RString))))\n\n# Double-quoted r-string\n\nfilter r\"version()\"\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(RString))))\n\n# Single-quoted s-string\n\nfilter s'version()'\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(SString))))\n\n# Double-quoted s-string\n\nfilter s\"version()\"\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(SString))))\n\n# Triple-quoted single-quoted string\n\nfilter '''Hello world!'''\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(String))))\n\n# Triple-quoted double-quoted string\n\nfilter \"\"\"Hello world!\"\"\"\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(String))))\n\n# Escape sequence\n\nfilter \"\\xff\"\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(String(Escape)))))\n"
  },
  {
    "path": "grammars/prql-lezer/test/test-prql.js",
    "content": "import { parser } from \"../dist/index.js\";\nimport { fileTests } from \"@lezer/generator/dist/test\";\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { fileURLToPath } from \"url\";\nlet caseDir = path.dirname(fileURLToPath(import.meta.url));\n\nfor (let file of fs.readdirSync(caseDir)) {\n  if (!/\\.txt$/.test(file)) continue;\n\n  let name = /^[^\\.]*/.exec(file)[0];\n  describe(name, () => {\n    for (let { name, run } of fileTests(\n      fs.readFileSync(path.join(caseDir, file), \"utf8\"),\n      file,\n    ))\n      it(name, () => run(parser));\n  });\n}\n"
  },
  {
    "path": "grammars/prql-lezer/test/tuples.txt",
    "content": "# Tuple on one line\n\ntest {foo, bar, baz}\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(Identifier,Identifier,Identifier)))))\n\n# Tuple on multiple lines\n\ntest {\n  foo,\n  bar,\n  baz\n}\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(Identifier,Identifier,Identifier)))))\n\n# Tuple on multiple lines with blank lines\n\ntest {\n\n  foo,\n\n  bar,\n\n  baz\n\n}\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(Identifier,Identifier,Identifier)))))\n\n# Tuple with key and value\n\ntest {foo=bar}\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(DeclarationTuple(DeclarationItem,Equals,Identifier))))))\n\n# Tuple with keys and values\n\ntest {identifier=identifier,int  =3, float=  3.14 ,\nstring    =    \"string\"\n}\n\n==>\n\nQuery(Pipeline(CallExpression(Identifier,ArgList(TupleExpression(DeclarationTuple(DeclarationItem,Equals,Identifier),DeclarationTuple(DeclarationItem,Equals,Integer),DeclarationTuple(DeclarationItem,Equals,Float),DeclarationTuple(DeclarationItem,Equals,String))))))\n"
  },
  {
    "path": "grammars/raku/META6.json",
    "content": "{\n  \"name\": \"PRQL\",\n  \"version\": \"0.0.1\",\n  \"description\": \"Grammar for PRQL\",\n  \"tags\": [\"data\", \"grammar\", \"pipeline\", \"sql\"],\n  \"source-url\": \"https://github.com/PRQL/prql/\",\n  \"support\": {\n    \"source\": \"https://github.com/PRQL/prql/\",\n    \"bugtracker\": \"https://github.com/PRQL/prql/issues\"\n  },\n  \"api\": \"1.0\",\n  \"production\": false,\n  \"auth\": \"github:PRQL\",\n  \"license\": \"Artistic-2.0\",\n  \"provides\": {\n    \"prql\": \"lib/prql.rakumod\"\n  },\n  \"raku\": \"6.*\",\n  \"meta-version\": \"1\"\n}\n"
  },
  {
    "path": "grammars/raku/README.md",
    "content": "# Raku grammar\n\nPRQL grammar for Raku.\n\n## Instructions\n\n```raku\nuse lib '.';\nuse prql;\n\nsay PRQL.parse('from employees');\nsay PRQL.parsefile('employees.prql');\n```\n\n## Installation\n\nTo install from source run:\n\n    zef install .\n\n## Tests\n\nTests can be run individually by specifying the test filename on the command\nline:\n\n    raku t/arithmetics.rakutest\n\nTo run all tests in the directory you have to install `prove6` using `zef`:\n\n    zef install App::Prove6\n    prove6 --lib t/\n\n## Documentation\n\n- https://docs.raku.org/language/grammar_tutorial\n- https://docs.raku.org/language/grammars\n"
  },
  {
    "path": "grammars/raku/lib/prql.rakumod",
    "content": "=begin pod\n\n=head1 NAME\n\nprql.rakumod - Grammar for PRQL.\n\n=head1 SYNOPSIS\n\n    use PRQL;\n\n    # Parse a simple PRQL query\n    say PRQL.parse('from employees');\n\n    # Parse a PRQL file\n    say PRQL.parsefile('employees.prql');\n\n=head1 DESCRIPTION\n\nPRQL grammar for Raku.\n\nPRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement.\n\n=head1 SEE ALSO\n\nL<https://prql-lang.org/> and L<https://github.com/PRQL/prql/>.\n\n=end pod\n\ngrammar PRQL {\n    token TOP {\n        <statement>*\n    }\n\n    rule statement {\n        | <doc-block>\n        | <comment>\n        | <query-definition>\n        | <module>\n        | <annotation>\n        | <variable-declaration>\n        | <pipeline-statement> <comment>?\n    }\n\n    rule query-definition {\n        'prql' <named-arg>+\n    }\n\n    rule module {\n        'module' <identifier> '{' <statement>* '}'\n    }\n\n    rule pipeline-statement {\n        <pipeline>\n    }\n\n    rule pipeline {\n        | <call-expression>+ % '|'\n        | <expression> '|' <identifier>\n    }\n\n    rule tuple-expression {\n        '{'\n        (\n        | <declaration-tuple>\n        | <call-expression>\n        | <expression>\n        | <case-branch>\n        )* %% ','\n        '}'\n    }\n\n    rule annotation {\n        '@{'\n        <declaration>? % ','\n        '}'\n    }\n\n    rule call-expression {\n        <identifier>\n        (\n        | <named-arg>\n        | <declaration>\n        | <test>\n        )+\n    }\n\n    rule named-arg {\n        <identifier> ':' <expression>\n    }\n\n    rule declaration {\n        <identifier> '=' <expression>\n    }\n\n    rule declaration-tuple {\n        <identifier> '=' <expression>\n    }\n\n    rule case-branch {\n        <expression> '=>' <expression>\n    }\n\n    rule case-expression {\n        'case' <tuple-expression>\n    }\n\n    rule nested-pipeline {\n        '(' <pipeline> ')'\n    }\n\n    # The name \"test\" here means equality testing. It is the name used in the Python grammar.\n    rule test {\n        <test-inner>\n    }\n\n    rule test-inner {\n        | <binary-test>\n        | '!' <test-inner>\n        | <expression>\n    }\n\n    rule binary-test {\n        | <expression> <logic-op> <expression>\n        | <expression> <compare-op> <expression>\n        | <expression> <arith-op> <expression>\n    }\n\n    token expression {\n        | 'this'\n        | 'that'\n        | 'null'\n        | <binary-expression>\n        | <unary-expression>\n        | <array-expression>\n        | <tuple-expression>\n        | <nested-pipeline>\n        | <case-expression>\n        | <date-time>\n        | <parameter>\n        | <parenthesized-expression>\n        | <range-expression>\n        | <identifier>\n        | <boolean>\n        | <time-unit>\n        | <number>\n        | <string>\n        | <f-string>\n        | <r-string>\n        | <s-string>\n    }\n\n    token boolean {\n        | 'true'\n        | 'false'\n    }\n\n    token variable-name {\n        <.ident>\n    }\n\n    token identifier {\n        <.ident>\n        ['.' [<.ident> | '*']]*\n    }\n\n    rule array-expression {\n        '['\n        (<test> | '*' <expression>)+ %% ','\n        ']'\n    }\n\n    rule binary-expression {\n        <identifier> <arith-op> <expression>\n    }\n\n    rule parenthesized-expression {\n        '(' <expression> ')'\n    }\n\n    rule unary-expression {\n        | ('+' | '-') <expression>\n        | '==' <identifier>\n    }\n\n    token number {\n        | <float>\n        | <integer>\n    }\n\n    rule variable-declaration {\n        'let' <variable-name> '=' (<nested-pipeline> | <lambda>)\n    }\n\n    rule lambda {\n        <lambda-param>* '->' <expression>\n    }\n\n    rule type-definition {\n        '<' <type-name> ('|' <type-name>)* '>'\n    }\n\n    rule type-name {\n        <identifier> <type-definition>?\n    }\n\n    rule lambda-param {\n        <identifier> <type-definition>? (':' <expression>)?\n    }\n\n    token integer {\n        | <.digit> [<.digit> | '_']* ['e' ['+' | '-']? <integer>]?\n        | '0x' [<.xdigit> | '_']+\n        | '0b' <[01_]>+\n        | '0o' <[0..7_]>+\n    }\n\n    token float {\n        \\d [\\d | '_']* '.' \\d [\\d | '_']* ['e' <integer>]?\n    }\n\n    token date {\n        \\d+ '-' \\d+ '-' \\d+\n    }\n\n    token time {\n        \\d+ ':' \\d+\n        [':' \\d+ ['.' \\d+]?]?\n    }\n\n    token date-time {\n        '@'\n        (\n        | <date> 'T' <time> ['Z' | ['-' | '+'] \\d+ ':' \\d+]?\n        | <date>\n        | <time>\n        )\n    }\n\n    token time-unit {\n        $<number>=\\d+\n        <dimension>\n    }\n\n    token escape {\n        '\\\\'\n        (\n        | 'x' <xdigit> <xdigit>\n        | 'u' '{' <xdigit>+ '}'\n        | <[bfnrt]>\n        )\n    }\n\n    token parameter {\n        '$'\n        \\w+\n    }\n\n    token doc-block {\n        '#!' .+? $$\n    }\n\n    token comment {\n        '#' .+? $$\n    }\n\n    token range-expression {\n        (<.digit>+)\n        '..'\n        (<.digit>+)\n    }\n\n    token dimension {\n        [\n        | microseconds\n        | milliseconds\n        | seconds\n        | minutes\n        | hours\n        | days\n        | weeks\n        | months\n        | years\n        ] <!ww>\n    }\n\n    token arith-op {\n        '+' | '-' | '*' | '/' | '%' | '//' | '**'\n    }\n\n    token compare-op {\n        '==' | '!=' | '~=' | '>=' | '<=' | '>' | '<' | 'in'\n    }\n\n    token logic-op {\n        '&&' | '||' | '??'\n    }\n\n    token f-string {\n        | 'f\"' (<-[\\\"]> | <escape>)* '\"'\n        | 'f\\'' (<-[\\']> | <escape>)* '\\''\n    }\n\n    token r-string {\n        | 'r\"' <-[\\\"]>* '\"'\n        | 'r\\'' <-[\\']>* '\\''\n    }\n\n    token s-string {\n        | 's\"' (<-[\\\"]> | <escape>)* '\"'\n        | 's\\'' (<-[\\']> | <escape>)* '\\''\n    }\n\n    token string {\n        | '\"\"\"' (<-[\\\"]> | <escape>)* '\"\"\"'\n        | '\"' (<-[\\\"]> | <escape>)* '\"'\n        | '\\'' (<-[\\']> | <escape>)* '\\''\n        | '\\'\\'\\'' (<-[\\']> | <escape>)* '\\'\\'\\''\n    }\n}\n"
  },
  {
    "path": "grammars/raku/t/arithmetics.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 6;\n\n# Plus\nok PRQL.parse('filter 10 + 10.5');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(integer,arith-op,float)))))\n\n# Minus\nok PRQL.parse('filter 10 - 10');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(integer,arith-op,integer)))))\n\n# Multiply\nok PRQL.parse('filter 10 * 10');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(integer,arith-op,integer)))))\n\n# Divide\nok PRQL.parse('filter 10 / 10');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(integer,arith-op,integer)))))\n\n# Exponentiation\nok PRQL.parse('filter 10 ** 10');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(integer,arith-op,integer)))))\n\n# Multiple ops\nok PRQL.parse('filter 10 + 10 + 10');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(binary-expression(integer,arith-op,integer),arith-op,integer)))))\n"
  },
  {
    "path": "grammars/raku/t/arrays.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 6;\n\n# Array on one line\nok PRQL.parse('filter [foo, bar, baz]');\n#query(pipeline(call-expression(identifier,arg-list(array-expression(identifier,identifier,identifier)))))\n\n# Array on multiple lines\nok PRQL.parse(q:to/END/);\nfilter [\n  foo,\n  bar,\n  baz\n]\nEND\n#query(pipeline(call-expression(identifier,arg-list(array-expression(identifier,identifier,identifier)))))\n\n# Array on multiple lines with blank lines\nok PRQL.parse(q:to/END/);\nfilter [\n\n  foo,\n\n  bar,\n\n  baz\n\n]\nEND\n#query(pipeline(call-expression(identifier,arg-list(array-expression(identifier,identifier,identifier)))))\n\n# Array of integers\nok PRQL.parse('filter [1, 2, 3]');\n#query(pipeline(call-expression(identifier,arg-list(array-expression(integer,integer,integer)))))\n\n# Array of floats\nok PRQL.parse('filter [1.1, 2.2, 3.3]');\n#query(pipeline(call-expression(identifier,arg-list(array-expression(float,float,float)))))\n\n# Array of strings\nok PRQL.parse('filter [\"string\", f\"format\", r\"raw\", s\"server\"]');\n#query(pipeline(call-expression(identifier,arg-list(array-expression(string,f-string,r-string,s-string)))))\n"
  },
  {
    "path": "grammars/raku/t/datetime.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 7;\n\n# Date YYYY-MM-DD\nok PRQL.parse('filter @1970-01-01');\n#query(pipeline(call-expression(identifier,arg-list(date-time))))\n\n# Time HH:MM\nok PRQL.parse('filter @08:30');\n#query(pipeline(call-expression(identifier,arg-list(date-time))))\n\n# Time HH:MM:SS\nok PRQL.parse('filter @12:00:00');\n#query(pipeline(call-expression(identifier,arg-list(date-time))))\n\n# Time HH:MM:SS.xxx\nok PRQL.parse('filter @12:00:00.500');\n#query(pipeline(call-expression(identifier,arg-list(date-time))))\n\n# Date and time\nok PRQL.parse('filter @1970-01-01T12:00:00');\n#query(pipeline(call-expression(identifier,arg-list(date-time))))\n\n# Date and time with timezone\nok PRQL.parse('filter @1970-01-01T12:00:00+01:00');\n#query(pipeline(call-expression(identifier,arg-list(date-time))))\n\n# Date and time in UTC\nok PRQL.parse('filter @1970-01-01T12:00:00Z');\n#query(pipeline(call-expression(identifier,arg-list(date-time))))\n"
  },
  {
    "path": "grammars/raku/t/full_queries.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 1;\n\nok PRQL.parse(q:to/END/);\n# Website showcasing example\nfrom invoices\nfilter invoice_date >= @1970-01-16\nderive {\n  transaction_fees = 0.8,\n  income = total - transaction_fees\n}\nfilter income > 1\ngroup customer_id (\n  aggregate {\n    average total\n  }\n)\nsort {-sum_income}\ntake 10\njoin c=customers (==customer_id)\nderive name = f\"{c.last_name}, {c.first_name}\"\nselect {\n  c.customer_id, name, sum_income\n}\nderive db_version = s\"version()\"\nEND\n#query(pipeline(call-expression(identifier,arg-list(identifier)),call-expression(identifier,arg-list(binary-expression(identifier,compare-op,date-time))),call-expression(identifier,arg-list(tuple-expression(declaration-tuple(declaration-item,equals,float),declaration-tuple(declaration-item,equals,binary-expression(identifier,arith-op,identifier))))),call-expression(identifier,arg-list(binary-expression(identifier,compare-op,integer))),call-expression(identifier,arg-list(identifier,nested-pipeline(pipeline(call-expression(identifier,arg-list(tuple-expression(call-expression(identifier,arg-list(identifier))))))))),call-expression(identifier,arg-list(tuple-expression(unary-expression(arith-op,identifier)))),call-expression(identifier,arg-list(integer)),call-expression(identifier,arg-list(declaration(declaration-item,equals,identifier),parenthesized-expression(unary-Expression(compare-op,identifier)))),call-expression(identifier,arg-list(declaration(declaration-item,equals,f-string))),call-expression(identifier,arg-list(tuple-expression(identifier,identifier,identifier))),call-expression(identifier,arg-list(declaration(declaration-item,equals,s-string)))))\n"
  },
  {
    "path": "grammars/raku/t/identifiers.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 3;\n\n# Basic identifier\nok PRQL.parse('filter foo');\n#query(pipeline(call-expression(identifier,arg-list(identifier))))\n\n# Identifier with underscore and digit\nok PRQL.parse('filter foo_123');\n#query(pipeline(call-expression(identifier,arg-list(identifier))))\n\n# Unicode identifier\nok PRQL.parse('filter räksmörgås');\n#query(pipeline(call-expression(identifier,arg-list(identifier))))\n"
  },
  {
    "path": "grammars/raku/t/misc.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 23;\n\n# Boolean: true\nok PRQL.parse('filter true');\n#query(pipeline(call-expression(identifier,arg-list(boolean))))\n\n# Boolean: false\nok PRQL.parse('filter false');\n#query(pipeline(call-expression(identifier,arg-list(boolean))))\n\n# Null\nok PRQL.parse('filter null');\n#query(pipeline(call-expression(identifier,arg-list(null))))\n\n# Keyword: this\nok PRQL.parse('filter this');\n#query(pipeline(call-expression(identifier,arg-list(this))))\n\n# Keyword: that\nok PRQL.parse('filter that');\n#query(pipeline(call-expression(identifier,arg-list(that))))\n\n# Annotation\nok PRQL.parse(q:to/END/);\n@{binding_strength=1}\nlet x = y -> z\nEND\n#query(annotation(declaration(declaration-item,equals,integer)),variable-declaration(let,variable-name,equals,lambda(lambda-param,identifier)))\n\n# Range: 10..20\nok PRQL.parse('filter 10..20');\n#query(pipeline(call-expression(identifier,arg-list(range-expression))))\n\n# Comment\nok PRQL.parse('filter 1 # Hello');\n#query(pipeline(call-expression(identifier,arg-list(integer))),comment)\n\n# Two comments\nok PRQL.parse(q:to/END/);\n# Hello\n# Bar\nEND\n#query(comment,comment)\n\n# Comment between pipes\nok PRQL.parse(q:to/END/);\nfrom foo\n# filter...\nselect bar\nEND\n#query(pipeline(call-expression(identifier,arg-list(identifier)),comment,call-expression(identifier,arg-list(identifier))))\n\n# Docblock\nok PRQL.parse(q:to/END/);\n#! Hello\nfilter 1\nEND\n#query(docblock,pipeline(call-expression(identifier,arg-list(integer))))\n\n# Variable declaration\nok PRQL.parse('let foo = (from foo)');\n#query(variable-declaration(let,variable-name,equals,nested-pipeline(pipeline(call-expression(identifier,arg-list(identifier))))))\n\n# Function declaration\nok PRQL.parse('let my_func = arg1 -> arg1');\n#query(variable-declaration(let,variable-name,equals,lambda(lambda-param,identifier)))\n\n# Function declaration with two args\nok PRQL.parse('let my_func = arg1 arg2 -> arg1 + arg2');\n#query(variable-declaration(let,variable-name,equals,lambda(lambda-param,lambda-param,binary-expression(identifier,arith-op,identifier))))\n\n# Function declaration with type annotation\nok PRQL.parse('let my_func = arg1<int32> -> arg1');\n#query(variable-declaration(let,variable-name,equals,lambda(lambda-param(type-definition(type-name)),identifier)))\n\n# Parameter\nok PRQL.parse('filter id == $1');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(identifier,compare-op,parameter)))))\n\n# Parenthesized expression\nok PRQL.parse('filter (1)');\n#query(pipeline(call-expression(identifier,arg-list(parenthesized-expression(integer)))))\n\n# Simple pipeline\nok PRQL.parse('from foo | select bar');\n#query(pipeline(call-expression(identifier,arg-list(identifier)),call-expression(identifier,arg-list(identifier))))\n\n# Derive\nok PRQL.parse(q:to/END/);\nderive {\n  transaction_fees = 0.8,\n  income = total - transaction_fees\n}\nEND\n#query(pipeline(call-expression(identifier,arg-list(tuple-expression(declaration-tuple(declaration-item,equals,float),declaration-tuple(declaration-item,equals,binary-expression(identifier,arith-op,identifier)))))))\n\n# Nested pipeline\nok PRQL.parse(q:to/END/);\ngroup customer_id (\n  aggregate {\n    average total\n  }\n)\nEND\n#query(pipeline(call-expression(identifier,arg-list(identifier,nested-pipeline(pipeline(call-expression(identifier,arg-list(tuple-expression(call-expression(identifier,arg-list(identifier)))))))))))\n\n# Tabs as spaces\nok PRQL.parse('let\t\tfoo\t\t=\t\t(foo 1)');\n#query(variable-declaration(let,variable-name,equals,nested-pipeline(pipeline(call-expression(identifier,arg-list(integer))))))\n\n# Module\nok PRQL.parse(q:to/END/);\nmodule foo {\n  let x = -> s\"foo()\"\n}\nEND\n#query(module(module,identifier,variable-declaration(let,variable-name,equals,lambda(s-string))))\n\n# Pipeline within tuple\nok PRQL.parse(q:to/END/);\nderive {\n  a = (b | math.abs)\n}\nEND\n#query(pipeline(call-expression(identifier,arg-list(tuple-expression(declaration-tuple(declaration-item,equals,nested-pipeline(pipeline(identifier,identifier))))))))\n"
  },
  {
    "path": "grammars/raku/t/numbers.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 9;\n\n# Integer\nok PRQL.parse('filter 123');\n#query(pipeline(call-expression(identifier,arg-list(integer))))\n\n# Integer with underscore\nok PRQL.parse('filter 123_456');\n#query(pipeline(call-expression(identifier,arg-list(integer))))\n\n# Integer with underscores\nok PRQL.parse('filter 123_456_789');\n#query(pipeline(call-expression(identifier,arg-list(integer))))\n\n# Decimal\nok PRQL.parse('filter 123.45');\n#query(pipeline(call-expression(identifier,arg-list(float))))\n\n# Scientific notation\nok PRQL.parse('filter 123e10');\n#query(pipeline(call-expression(identifier,arg-list(integer))))\n\n# Number with time unit\nok PRQL.parse('filter 5years');\n#query(pipeline(call-expression(identifier,arg-list(time-unit))))\n\n# Binary notation\nok PRQL.parse('filter 0b1111');\n#query(pipeline(call-expression(identifier,arg-list(integer))))\n\n# Hex notation\nok PRQL.parse('filter 0xff');\n#query(pipeline(call-expression(identifier,arg-list(integer))))\n\n# Octal notation\nok PRQL.parse('filter 0o777');\n#query(pipeline(call-expression(identifier,arg-list(integer))))\n"
  },
  {
    "path": "grammars/raku/t/operators.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 10;\n\n# == Equals\nok PRQL.parse('filter foo == bar');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(identifier,compare-op,identifier)))))\n\n# != Not equals\nok PRQL.parse('filter foo != bar');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(identifier,compare-op,identifier)))))\n\n# >= Greater than\nok PRQL.parse('filter foo >= bar');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(identifier,compare-op,identifier)))))\n\n# <= Less than\nok PRQL.parse('filter foo <= bar');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(identifier,compare-op,identifier)))))\n\n# ~= Regex match\nok PRQL.parse('filter foo ~= bar');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(identifier,compare-op,identifier)))))\n\n# && And\nok PRQL.parse('filter foo && bar');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(identifier,logic-op,identifier)))))\n\n# || Or\nok PRQL.parse('filter foo || bar');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(identifier,logic-op,identifier)))))\n\n# ?? Coalesce\nok PRQL.parse('filter foo ?? bar');\n#query(pipeline(call-expression(identifier,arg-list(binary-expression(identifier,logic-op,identifier)))))\n\n# Unary operator -\nok PRQL.parse('sort { -name }');\n#query(pipeline(call-expression(identifier,arg-list(tuple-expression(unary-expression(arith-op,identifier))))))\n\n# Unary operator ==\nok PRQL.parse('join customers (==customer_id)');\n#query(pipeline(call-expression(identifier,arg-list(identifier,parenthesized-expression(unary-expression(compare-op,identifier))))))\n"
  },
  {
    "path": "grammars/raku/t/strings.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 11;\n\n# Single-quoted string\nok PRQL.parse(\"filter 'Hello'\");\n#query(pipeline(call-expression(identifier,arg-list(string))))\n\n# Double-quoted string\nok PRQL.parse('filter \"Hello\"');\n#query(pipeline(call-expression(identifier,arg-list(string))))\n\n# Single-quoted f-string\nok PRQL.parse(\"filter f'Hello \\{name\\}!'\");\n#query(pipeline(call-expression(identifier,arg-list(f-string))))\n\n# Double-quoted f-string\nok PRQL.parse('filter f\"Hello {name}!\"');\n#query(pipeline(call-expression(identifier,arg-list(f-string))))\n\n# Single-quoted r-string\nok PRQL.parse(\"filter r'version()'\");\n#query(pipeline(call-expression(identifier,arg-list(r-string))))\n\n# Double-quoted r-string\nok PRQL.parse('filter r\"version()\"');\n#query(pipeline(call-expression(identifier,arg-list(r-string))))\n\n# Single-quoted s-string\nok PRQL.parse(\"filter s'version()'\");\n#query(pipeline(call-expression(identifier,arg-list(s-string))))\n\n# Double-quoted s-string\nok PRQL.parse('filter s\"version()\"');\n#query(pipeline(call-expression(identifier,arg-list(s-string))))\n\n# Triple-quoted single-quoted string\nok PRQL.parse(\"filter '''Hello world!'''\");\n#query(pipeline(call-expression(identifier,arg-list(string))))\n\n# Triple-quoted double-quoted string\nok PRQL.parse('filter \"\"\"Hello world!\"\"\"');\n#query(pipeline(call-expression(identifier,arg-list(string))))\n\n# Escape sequence\nok PRQL.parse('filter \"\\xff\"');\n#query(pipeline(call-expression(identifier,arg-list(string(escape)))))\n"
  },
  {
    "path": "grammars/raku/t/tuples.rakutest",
    "content": "use Test;\nuse prql;\n\nplan 5;\n\n# Tuple on one line\nok PRQL.parse('test {foo, bar, baz}');\n#query(pipeline(call-expression(identifier,arg-list(tuple-expression(identifier,identifier,identifier)))))\n\n# Tuple on multiple lines\nok PRQL.parse(q:to/END/);\ntest {\n  foo,\n  bar,\n  baz\n}\nEND\n#query(pipeline(call-expression(identifier,arg-list(tuple-expression(identifier,identifier,identifier)))))\n\n# Tuple on multiple lines with blank lines\nok PRQL.parse(q:to/END/);\ntest {\n\n  foo,\n\n  bar,\n\n  baz\n\n}\nEND\n#query(pipeline(call-expression(identifier,arg-list(tuple-expression(identifier,identifier,identifier)))))\n\n# Tuple with key and value\nok PRQL.parse('test {foo=bar}');\n#query(pipeline(call-expression(identifier,arg-list(tuple-expression(declaration-tuple(declaration-item,equals,identifier))))))\n\n# Tuple with keys and values\nok PRQL.parse(q:to/END/);\ntest {identifier=identifier,int  =3, float=  3.14 ,\nstring    =    \"string\"\n}\nEND\n#query(pipeline(call-expression(identifier,arg-list(tuple-expression(declaration-tuple(declaration-item,equals,identifier),declaration-tuple(declaration-item,equals,integer),declaration-tuple(declaration-item,equals,float),declaration-tuple(declaration-item,equals,string))))))\n"
  },
  {
    "path": "grammars/vim/README.md",
    "content": "# Syntax highlighting for Vim\n\nThis is a syntax highlighting file for Vim and Neovim.\n\n## Installation\n\n### For Vim\n\nCopy the `prql.vim` file to:\n\n    ~/.vim/syntax/\n\nThen, edit your `~/.vimrc` file and add the following:\n\n```vim\naugroup PrqlFileType\n  autocmd!\n  autocmd BufRead,BufNewFile *.prql setfiletype prql\naugroup END\n```\n\n### For Neovim\n\nCopy the `prql.vim` file to:\n\n    ~/.config/nvim/syntax/\n\nThen, edit your `~/.config/nvim/init.vim` file.\n"
  },
  {
    "path": "prqlc/README.md",
    "content": "# PRQL compiler\n\nReference implementation of a compiler from PRQL to SQL, written in Rust.\n\nThe bindings for other programming languages can be found in\n[./bindings/](./bindings/).\n\n`prqlc` is the CLI for the compiler.\n"
  },
  {
    "path": "prqlc/Taskfile.yaml",
    "content": "# prqlc Taskfile - core development tasks for fast iteration.\n#\n# This file contains tasks for prqlc development:\n# - test: Fast inner-loop test (~5s) - run during development\n# - test-all: Comprehensive test with coverage (~30s) - run before committing\n# - pull-request: Complete PR checks (test-all + lint)\n#\n# For repo-wide operations (lint, setup), use tasks from the root Taskfile.\n\nversion: \"3\"\n\nincludes:\n  bindings-python:\n    taskfile: ./bindings/prqlc-python\n    dir: ./bindings/prqlc-python\n\nvars:\n  packages_core: -p prqlc-parser -p prqlc\n  packages_addon: -p prqlc-macros -p compile-files\n  packages_bindings: -p prql -p prql-java -p prqlc-js -p prqlc-c -p prqlc-python\n\ntasks:\n  test:\n    desc: Fast inner-loop test, accepting snapshots (run during development).\n    env:\n      NEXTEST_STATUS_LEVEL: fail\n      NEXTEST_FINAL_STATUS_LEVEL: slow\n      NEXTEST_HIDE_PROGRESS_BAR: \"true\"\n    cmds:\n      # --dnd: nextest doesn't support doctests, so we run them separately below\n      - cargo insta test --accept --dnd {{.packages_core}} --test-runner=nextest\n      - cargo test --doc {{.packages_core}}\n      - cargo clippy --fix --allow-dirty --allow-staged {{.packages_core}}\n\n  test-all:\n    desc: Comprehensive prqlc test with coverage, accepting snapshots.\n    env:\n      NEXTEST_STATUS_LEVEL: fail\n      NEXTEST_FINAL_STATUS_LEVEL: slow\n      NEXTEST_HIDE_PROGRESS_BAR: \"true\"\n    cmds:\n      - cargo insta test --accept --dnd --features=default,test-dbs\n        --test-runner=nextest --unreferenced=auto {{.packages_core}}\n        {{.packages_addon}} {{.packages_bindings}}\n      - cargo test --doc --features=default,test-dbs {{.packages_core}}\n        {{.packages_addon}} {{.packages_bindings}}\n      - cargo llvm-cov --lcov --output-path lcov.info\n        --features=default,test-dbs nextest {{.packages_core}}\n        {{.packages_addon}} {{.packages_bindings}}\n\n  pull-request:\n    desc: Complete PR checks (combines test-all + lint).\n    cmds:\n      - task: test-all\n      # Lint is repo-wide, so call it from root\n      - task: :lint\n"
  },
  {
    "path": "prqlc/bindings/dotnet/.gitignore",
    "content": "bin\nobj\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/Message.cs",
    "content": "using System.Runtime.InteropServices;\n\nnamespace Prql.Compiler\n{\n    /// <summary>\n    /// Compile result message.\n    /// </summary>\n    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]\n    public struct Message\n    {\n        /// <summary>\n        /// Message kind. Currently only Error is implemented.\n        /// </summary>\n        public MessageKind Kind { get; set; }\n\n        /// <summary>\n        /// Machine-readable identifier of the error.\n        /// </summary>\n        public string Code { get; set; }\n\n        /// <summary>\n        /// Plain text of the error.\n        /// </summary>\n        public string Reason { get; set; }\n\n        /// <summary>\n        /// A list of suggestions of how to fix the error.\n        /// </summary>\n        public string Hint { get; set; }\n\n        /// <summary>\n        /// Character offset of error origin within a source file.\n        /// </summary>\n        public Span Span { get; set; }\n\n        /// <summary>\n        /// Annotated code, containing cause and hints.\n        /// </summary>\n        public string Display { get; set; }\n\n        /// <summary>\n        /// Line and column number of error origin within a source file.\n        /// </summary>\n        public SourceLocation Location { get; set; }\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/MessageKind.cs",
    "content": "using System;\n\nnamespace Prql.Compiler\n{\n    /// <summary>\n    /// Compile message kind. Currently only Error is implemented.\n    /// </summary>\n    [Serializable]\n    public enum MessageKind\n    {\n        /// <summary>\n        /// Error message.\n        /// </summary>\n        Error,\n        /// <summary>\n        /// Warning message.\n        /// </summary>\n        Warning,\n        /// <summary>\n        /// Lint message.\n        /// </summary>\n        Lint\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/NativePrqlCompilerOptions.cs",
    "content": "namespace Prql.Compiler\n{\n    internal struct NativePrqlCompilerOptions\n    {\n        public bool Format;\n        public string Target;\n        public bool SignatureComment;\n\n        public NativePrqlCompilerOptions(PrqlCompilerOptions options)\n        {\n            Format = options.Format;\n            Target = options.Target;\n            SignatureComment = options.SignatureComment;\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/NativeResult.cs",
    "content": "using System;\n\nnamespace Prql.Compiler\n{\n    internal struct NativeResult\n    {\n#pragma warning disable CS0649 // Field is never assigned to\n        public string Output;\n        public IntPtr Messages;\n        public int MessagesLen;\n#pragma warning restore CS0649 // Field is never assigned to\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/PrqlCompiler.cs",
    "content": "using System;\nusing System.Runtime.InteropServices;\n\nnamespace Prql.Compiler\n{\n    /// <summary>\n    /// The PRQL compiler transpiles PRQL queries.\n    /// </summary>\n    public static class PrqlCompiler\n    {\n        /// <summary>\n        /// Compile a PRQL string into a SQL string.\n        /// </summary>\n        /// <param name=\"prqlQuery\">A PRQL query.</param>\n        /// <returns>SQL query.</returns>\n        /// <exception cref=\"ArgumentException\"><paramref name=\"prqlQuery\"/> is null or empty.</exception>\n        /// <exception cref=\"FormatException\"><paramref name=\"prqlQuery\"/> cannot be compiled.</exception>\n        public static Result Compile(string prqlQuery)\n        {\n            if (string.IsNullOrEmpty(prqlQuery))\n            {\n                throw new ArgumentException(nameof(prqlQuery));\n            }\n\n            var options = new PrqlCompilerOptions();\n\n            return Compile(prqlQuery, options);\n        }\n\n        /// <summary>\n        /// Compile a PRQL string into a SQL string.\n        /// </summary>\n        /// <param name=\"prqlQuery\">A PRQL query.</param>\n        /// <param name=\"options\">PRQL compiler options.</param>\n        /// <returns>SQL query.</returns>\n        /// <exception cref=\"ArgumentException\"><paramref name=\"prqlQuery\"/> is null or empty.</exception>\n        /// <exception cref=\"ArgumentNullException\"><paramref name=\"options\"/> is <c>null</c>.</exception>\n        /// <exception cref=\"FormatException\"><paramref name=\"prqlQuery\"/> cannot be compiled.</exception>\n        public static Result Compile(string prqlQuery, PrqlCompilerOptions options)\n        {\n            if (string.IsNullOrEmpty(prqlQuery))\n            {\n                throw new ArgumentException(nameof(prqlQuery));\n            }\n\n            if (options is null)\n            {\n                throw new ArgumentException(nameof(options));\n            }\n\n            var nativeOptions = new NativePrqlCompilerOptions(options);\n            var nativeResult = CompileExtern(prqlQuery, ref nativeOptions);\n            var result = new Result(nativeResult);\n\n            return result;\n        }\n\n        /// <summary>\n        /// Build PL AST from a PRQL string.\n        /// </summary>\n        /// <param name=\"prqlQuery\">A PRQL query.</param>\n        /// <returns>JSON.</returns>\n        /// <exception cref=\"ArgumentException\"><paramref name=\"prqlQuery\"/> is null or empty.</exception>\n        /// <exception cref=\"FormatException\"><paramref name=\"prqlQuery\"/> cannot be compiled.</exception>\n        /// <remarks>https://docs.rs/prqlc/latest/</remarks>\n        public static Result PrqlToPl(string prqlQuery)\n        {\n            if (string.IsNullOrEmpty(prqlQuery))\n            {\n                throw new ArgumentException(nameof(prqlQuery));\n            }\n\n            var nativeResult = PrqlToPlExtern(prqlQuery);\n            var result = new Result(nativeResult);\n\n            return result;\n        }\n\n        /// <summary>\n        /// Finds variable references, validates functions calls, determines frames and converts PL to RQ.\n        /// </summary>\n        /// <param name=\"plJson\">A PRQL query.</param>\n        /// <returns>JSON.</returns>\n        /// <exception cref=\"ArgumentException\"><paramref name=\"plJson\"/> is null or empty.</exception>\n        /// <exception cref=\"FormatException\"><paramref name=\"plJson\"/> cannot be compiled.</exception>\n        /// <remarks>https://docs.rs/prqlc/latest/</remarks>\n        public static Result PlToRq(string plJson)\n        {\n            if (string.IsNullOrEmpty(plJson))\n            {\n                throw new ArgumentException(nameof(plJson));\n            }\n\n            var nativeResult = PlToRqExtern(plJson);\n            var result = new Result(nativeResult);\n\n            return result;\n        }\n\n        /// <summary>\n        /// Convert RQ AST into an SQL string.\n        /// </summary>\n        /// <param name=\"rqJson\">RQ string in JSON format.</param>\n        /// <param name=\"options\">PRQL compiler options.</param>\n        /// <returns>JSON.</returns>\n        /// <exception cref=\"ArgumentException\"><paramref name=\"prqlQuery\"/> is null or empty.</exception>\n        /// <exception cref=\"ArgumentNullException\"><paramref name=\"options\"/> is <c>null</c>.</exception>\n        /// <exception cref=\"FormatException\"><paramref name=\"prqlQuery\"/> cannot be compiled.</exception>\n        /// <remarks>https://docs.rs/prqlc/latest/</remarks>\n        public static Result RqToSql(string rqJson, PrqlCompilerOptions options)\n        {\n            if (string.IsNullOrEmpty(rqJson))\n            {\n                throw new ArgumentException(nameof(rqJson));\n            }\n\n            if (options is null)\n            {\n                throw new ArgumentException(nameof(options));\n            }\n\n            var nativeOptions = new NativePrqlCompilerOptions(options);\n            var nativeResult = RqToSqlExtern(rqJson, ref nativeOptions);\n            var result = new Result(nativeResult);\n\n            return result;\n        }\n\n        [DllImport(\"libprqlc_c\", EntryPoint = \"compile\")]\n        private static extern NativeResult CompileExtern(string prqlQuery, ref NativePrqlCompilerOptions options);\n\n        [DllImport(\"libprqlc_c\", EntryPoint = \"prql_to_pl\")]\n        private static extern NativeResult PrqlToPlExtern(string prqlQuery);\n\n        [DllImport(\"libprqlc_c\", EntryPoint = \"pl_to_rq\")]\n        private static extern NativeResult PlToRqExtern(string plJson);\n\n        [DllImport(\"libprqlc_c\", EntryPoint = \"rq_to_sql\")]\n        private static extern NativeResult RqToSqlExtern(string rqJson, ref NativePrqlCompilerOptions options);\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/PrqlCompiler.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>netstandard2.0</TargetFramework>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <PackageId>Prql.Compiler</PackageId>\n    <PackageVersion>0.1.0</PackageVersion>\n    <Title>PRQL Compiler</Title>\n    <Authors>PRQL</Authors>\n    <Copyright>Copyright © The PRQL Project 2023</Copyright>\n    <Description>.NET bindings for the PRQL compiler. PRQL is a modern language for transforming data</Description>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n    <PackageProjectUrl>https://prql-lang.org/</PackageProjectUrl>\n    <PackageReadmeFile>README.md</PackageReadmeFile>\n    <PackageTags>prql;sql</PackageTags>\n    <RepositoryUrl>https://github.com/PRQL/prql</RepositoryUrl>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <None Include=\"..\\README.md\">\n      <Pack>True</Pack>\n      <PackagePath>\\</PackagePath>\n    </None>\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Update=\"libprqlc_c.dll\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </None>\n    <None Update=\"libprqlc_c.dylib\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </None>\n    <None Update=\"libprqlc_c.so\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </None>\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/PrqlCompilerOptions.cs",
    "content": "using System.Runtime.InteropServices;\n\nnamespace Prql.Compiler\n{\n    /// <summary>\n    /// Compilation options for SQL backend of the compiler.\n    /// </summary>\n    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]\n    public class PrqlCompilerOptions\n    {\n        /// <summary>\n        /// Pass generated SQL string through a formatter that splits it into\n        /// multiple lines and prettifies indentation and spacing.\n        /// </summary>\n        /// <remarks>Defaults to <c>true</c>.</remarks>\n        public bool Format { get; set; } = true;\n\n        /// <summary>\n        /// Target and dialect to compile to.\n        /// </summary>\n        public string Target { get; set; }\n\n        /// <summary>\n        /// Emits the compiler signature as a comment after generated SQL.\n        /// </summary>\n        /// <remarks>Defaults to <c>true</c>.</remarks>\n        public bool SignatureComment { get; set; } = true;\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/Result.cs",
    "content": "using System.Collections.Generic;\nusing System.Linq;\nusing System.Runtime.InteropServices;\n\nnamespace Prql.Compiler\n{\n    /// <summary>\n    /// Result of compilation.\n    /// </summary>\n    public class Result\n    {\n        private readonly IReadOnlyCollection<Message> _messages;\n\n        internal Result(NativeResult result)\n        {\n            Output = result.Output;\n\n            var messages = new List<Message>();\n\n            for (var i = 0; i < result.MessagesLen; i++)\n            {\n                messages.Add(Marshal.PtrToStructure<Message>(result.Messages));\n            }\n\n            _messages = messages.ToList().AsReadOnly();\n        }\n\n        /// <summary>\n        /// The compiler output.\n        /// </summary>\n        public string Output { get; }\n\n        /// <summary>\n        /// Error, warning and lint messages.\n        /// </summary>\n        public IReadOnlyCollection<Message> Messages => _messages;\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/SourceLocation.cs",
    "content": "using System.Runtime.InteropServices;\n\nnamespace Prql.Compiler\n{\n    /// <summary>\n    /// Location within a source file.\n    /// </summary>\n    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]\n    public struct SourceLocation\n    {\n        /// <summary>\n        /// Start line.\n        /// </summary>\n        public int StartLine { get; set; }\n\n        /// <summary>\n        /// Start column.\n        /// </summary>\n        public int StartCol { get; set; }\n\n        /// <summary>\n        /// End line.\n        /// </summary>\n        public int EndLine { get; set; }\n\n        /// <summary>\n        /// End column.\n        /// </summary>\n        public int EndCol { get; set; }\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler/Span.cs",
    "content": "using System.Runtime.InteropServices;\n\nnamespace Prql.Compiler\n{\n    /// <summary>\n    /// Identifier of a location in source.\n    /// Contains offsets in terms of chars.\n    /// </summary>\n    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]\n    public struct Span\n    {\n        /// <summary>\n        /// Start offset.\n        /// </summary>\n        public int Start { get; set; }\n\n        /// <summary>\n        /// End offset.\n        /// </summary>\n        public int End { get; set; }\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler.Tests/CompilerTest.cs",
    "content": "using Prql.Compiler;\n\nnamespace Prql.Compiler.Tests;\n\nsealed public class CompilerTest\n{\n    [Fact]\n    public void ToCompile_Works()\n    {\n    \t// Arrange\n        var expected = \"SELECT * FROM employees\";\n        var options = new PrqlCompilerOptions\n        {\n            Format = false,\n            SignatureComment = false,\n            Target = \"sql.mssql\"\n        };\n\n        // Act\n        var result = PrqlCompiler.Compile(\"from employees\", options);\n\n        // Assert\n        Assert.Equal(expected, result.Output);\n    }\n\n    [Fact]\n    public void TestOtherFunctions()\n    {\n        // Arrange\n        var query = \"\"\"\n            let a = (from employees | take 10)\n\n            from a | select {first_name}\n            \"\"\";\n        var options = new PrqlCompilerOptions();\n\n        // Act and assert\n        var pl = PrqlCompiler.PrqlToPl(query);\n        Assert.Empty(pl.Messages);\n\n        var rq = PrqlCompiler.PlToRq(pl.Output);\n        Assert.Empty(rq.Messages);\n\n        var via_json = PrqlCompiler.RqToSql(rq.Output, options);\n        Assert.Empty(via_json.Messages);\n\n        var direct = PrqlCompiler.Compile(query);\n        Assert.Empty(direct.Messages);\n\n        Assert.Equal(via_json.Messages, direct.Messages);\n        Assert.Equal(via_json.Output, direct.Output);\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler.Tests/PrqlCompiler.Tests.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net7.0</TargetFramework>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <Nullable>enable</Nullable>\n\n    <IsPackable>false</IsPackable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.3.2\" />\n    <PackageReference Include=\"xunit\" Version=\"2.4.2\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\" Version=\"2.4.5\">\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n      <PrivateAssets>all</PrivateAssets>\n    </PackageReference>\n    <PackageReference Include=\"coverlet.collector\" Version=\"3.1.2\">\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n      <PrivateAssets>all</PrivateAssets>\n    </PackageReference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\PrqlCompiler\\PrqlCompiler.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "prqlc/bindings/dotnet/PrqlCompiler.Tests/Usings.cs",
    "content": "global using Xunit;\n"
  },
  {
    "path": "prqlc/bindings/dotnet/README.md",
    "content": "# prql-dotnet\n\n`prql-net` offers PRQL bindings for .NET bindings as a `netstandard2.0` library.\n\nIt provides the `PrqlCompiler` class which contains the `ToJson` and `ToSql`\nstatic methods.\n\nIt's still at an early stage, and isn't published to NuGet. Contributions are\nwelcome.\n\n## Installation\n\nMake sure that `libprqlc_c.so` (Linux), `libprqlc_c.dylib` (macOS) or\n`libprqlc_c.dll` (Windows) is in the project's `bin` directory together with\n`PrqlCompiler.dll` and the rest of the project's compiled files. I.e.\n`{your_project}/bin/Debug/net7.0/`.\n\nThe `libprqlc_c` library gets dynamically imported at runtime.\n\n## Usage\n\n```csharp\nusing Prql.Compiler;\n\nvar options = new PrqlCompilerOptions\n{\n    Format = false,\n    SignatureComment = false,\n};\nvar sql = PrqlCompiler.Compile(\"from employees\", options);\nConsole.WriteLine(sql);\n```\n\n## TODO\n\nThis is currently at 0.1.0 because we're waiting to update prqlc-c for the\nlatest API. When we've done that, we can match the version here with the broader\nPRQL version.\n"
  },
  {
    "path": "prqlc/bindings/dotnet/prql-net.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.0.31903.59\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"PrqlCompiler\", \"PrqlCompiler\\PrqlCompiler.csproj\", \"{339EA2A6-23D2-4938-884F-052431AC0674}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"PrqlCompiler.Tests\", \"PrqlCompiler.Tests\\PrqlCompiler.Tests.csproj\", \"{78C1AD08-6FF5-444E-9298-385887ABAA80}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{339EA2A6-23D2-4938-884F-052431AC0674}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{339EA2A6-23D2-4938-884F-052431AC0674}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{339EA2A6-23D2-4938-884F-052431AC0674}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{339EA2A6-23D2-4938-884F-052431AC0674}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{78C1AD08-6FF5-444E-9298-385887ABAA80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{78C1AD08-6FF5-444E-9298-385887ABAA80}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{78C1AD08-6FF5-444E-9298-385887ABAA80}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{78C1AD08-6FF5-444E-9298-385887ABAA80}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "prqlc/bindings/elixir/.formatter.exs",
    "content": "# Used by \"mix format\"\n[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": "prqlc/bindings/elixir/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\nprql_elixir-*.tar\n\n# Temporary files, for example, from tests.\n/tmp/\n\n/priv/native/*\n"
  },
  {
    "path": "prqlc/bindings/elixir/README.md",
    "content": "# PRQL\n\n[PRQL](https://prql-lang.org/) bindings for Elixir.\n\n## Installation\n\n```elixir\ndef deps do\n  [\n    {:prql, \"~> 0.1.0\"}\n  ]\nend\n```\n\n## Basic Usage\n\n```elixir\n  iex> PRQL.compile(\"from customers\")\n      {:ok, \"SELECT\\n  *\\nFROM\\n  customers\\n\\n-- Generated by PRQL compiler version 0.3.1 (https://prql-lang.org)\\n\"}\n\n\n  iex> PRQL.compile(\"from customers\\ntake 10\", dialect: :mssql)\n  {:ok, \"SELECT\\n  *\\nFROM\\n  customers\\nORDER BY\\n  (\\n    SELECT\\n      NULL\\n  ) OFFSET 0 ROWS\\nFETCH FIRST\\n  10 ROWS ONLY\\n\\n-- Generated by PRQL compiler version 0.3.1 (https://prql-lang.org)\\n\"}\n```\n\n## Development\n\nWe are in the early stages of developing Elixir bindings.\n\nWe're using `Rustler` to provide Rust bindings for `prqlc`.\n\nCurrently using the bindings in an Elixir project requires compiling the Rust\ncrate from this repo:\n\n- Install dependencies with `mix deps.get`\n- Compile project `mix compile`\n- Run tests `mix test`\n\nFuture work includes publishing pre-compiled artifacts, so Elixir projects can\nrun PRQL without needing a Rust toolchain.\n"
  },
  {
    "path": "prqlc/bindings/elixir/lib/prql/errors.ex",
    "content": "defmodule PRQL.PRQLError do\n  @moduledoc \"\"\"\n  Represents an error returned from PRQL compiler.\n\n  `:error` contains the message from compiler as a **JSON string**.\n  \"\"\"\n  defexception [:message, :error]\n\n  @impl true\n  def exception(err) do\n    %__MODULE__{message: \"Error compiling PRQL query\", error: err}\n  end\nend\n"
  },
  {
    "path": "prqlc/bindings/elixir/lib/prql/native.ex",
    "content": "defmodule PRQL.Native do\n  @moduledoc false\n  use Rustler, otp_app: :prql\n\n  def compile(_prql_query, _options), do: e()\n\n  def prql_to_pl(_prql_query), do: e()\n\n  def pl_to_rq(_pl_json), do: e()\n\n  def rq_to_sql(_rq_json), do: e()\n\n  defp e(), do: :erlang.nif_error(:nif_not_loaded)\nend\n\ndefmodule PRQL.Native.CompileOptions do\n  @typedoc \"\"\"\n  Dialect used for SQL generation\n  \"\"\"\n  @type target() ::\n          :generic\n          | :mssql\n          | :mysql\n          | :postgres\n          | :ansi\n          | :bigquery\n          | :clickhouse\n          | :glaredb\n          | :redshift\n          | :sqlite\n          | :snowflake\n\n  @type t :: %__MODULE__{\n          target: target(),\n          format: boolean(),\n          signature_comment: boolean()\n        }\n\n  defstruct target: :generic, format: true, signature_comment: true\nend\n"
  },
  {
    "path": "prqlc/bindings/elixir/lib/prql.ex",
    "content": "defmodule PRQL do\n  @moduledoc \"\"\"\n  Documentation for `PRQL`.\n\n  This module provide Elixir bindings for [PRQL](https://prql-lang.org/).\n\n  PRQL is a modern language for transforming data.\n  \"\"\"\n  alias PRQL.Native.CompileOptions\n\n  @type target() ::\n          :generic\n          | :mssql\n          | :mysql\n          | :postgres\n          | :ansi\n          | :bigquery\n          | :clickhouse\n          | :glaredb\n          | :redshift\n          | :sqlite\n          | :snowflake\n  @type format_opt :: {:format, boolean()}\n  @type signature_comment_opt :: {:signature_comment, boolean()}\n  @type target_opt :: {:target, target()}\n  @type compile_opts :: format_opt() | signature_comment_opt() | target_opt()\n\n  @doc ~S\"\"\"\n  Compile a `PRQL` query to `SQL` query.\n\n  Returns generated SQL query on success in shape of `{:ok, sql}`. This SQL query can\n  be safely fed into a SQL driver to get the output.\n\n  On error, `{:error, reason}` is returned where `reason` is a `JSON` string.\n  Use any `JSON` encoder library to encode it.\n\n  ## Options\n\n    * `:target` - Dialect used for generate SQL. Accepted values are\n    `:generic`, `:mssql`, `:mysql`, `:postgres`, `:ansi`, `:bigquery`,\n    `:clickhouse`, `:glaredb`, `:sqlite`, `:snowflake`\n\n    * `:format` - Formats the output, defaults to `true`\n\n    * `signature_comment` - Set the signature comment generated by PRQL, defaults to `true`\n\n\n  ## Examples\n\n  Using default `Generic` target:\n      iex> PRQL.compile(\"from customers\", signature_comment: false)\n      {:ok, \"SELECT\\n  *\\nFROM\\n  customers\\n\"}\n\n\n  Using `MSSQL` target:\n      iex> PRQL.compile(\"from customers\\ntake 10\", target: :mssql, signature_comment: false)\n      {:ok, \"SELECT\\n  *\\nFROM\\n  customers\\nORDER BY\\n  (\\n    SELECT\\n      NULL\\n  ) OFFSET 0 ROWS\\nFETCH FIRST\\n  10 ROWS ONLY\\n\"}\n  \"\"\"\n  @spec compile(binary(), [compile_opts()]) :: {:ok, binary()} | {:error, binary()}\n  def compile(prql_query, opts \\\\ []) when is_binary(prql_query) and is_list(opts) do\n    PRQL.Native.compile(prql_query, struct(CompileOptions, opts))\n  end\n\n  @doc \"\"\"\n  The same as `compile/2` but raises `PRQL.PRQLError` exception in case of error\n  \"\"\"\n  @spec compile!(binary(), [compile_opts()]) :: binary()\n  def compile!(prql_query, opts \\\\ []) do\n    case compile(prql_query, opts) do\n      {:ok, result} -> result\n      {:error, reason} -> raise PRQL.PRQLError, reason\n    end\n  end\n\n  @doc \"\"\"\n  PRQL to PL AST\n  \"\"\"\n  @spec prql_to_pl(binary()) :: {:ok, binary()} | {:error, binary()}\n  def prql_to_pl(prql_query) when is_binary(prql_query) do\n    PRQL.Native.prql_to_pl(prql_query)\n  end\n\n  @doc \"\"\"\n  The same as `prql_to_pl/1` but raises `PRQL.PRQLError` exception in case of error\n  \"\"\"\n  @spec prql_to_pl!(binary()) :: binary()\n  def prql_to_pl!(prql_query) when is_binary(prql_query) do\n    case prql_to_pl(prql_query) do\n      {:ok, result} -> result\n      {:error, reason} -> raise PRQL.PRQLError, reason\n    end\n  end\n\n  @doc \"\"\"\n  PL AST to RQ\n  \"\"\"\n  @spec pl_to_rq(binary()) :: {:ok, binary()} | {:error, binary()}\n  def pl_to_rq(pl_json) do\n    PRQL.Native.pl_to_rq(pl_json)\n  end\n\n  @doc \"\"\"\n  The same as `pl_to_rq/1` but raises `PRQL.PRQLError` exception in case of error\n  \"\"\"\n  @spec pl_to_rq!(binary()) :: binary()\n  def pl_to_rq!(pl_json) do\n    case pl_to_rq(pl_json) do\n      {:ok, result} -> result\n      {:error, reason} -> raise PRQL.PRQLError, reason\n    end\n  end\n\n  @doc \"\"\"\n  RQ to SQL\n  \"\"\"\n  @spec rq_to_sql(binary()) :: {:ok, binary()} | {:error, binary()}\n  def rq_to_sql(rq_json) do\n    PRQL.Native.rq_to_sql(rq_json)\n  end\n\n  @doc \"\"\"\n  The same as `rq_to_sql/1` but raises `PRQL.PRQLError` exception in case of error\n  \"\"\"\n  @spec rq_to_sql!(binary()) :: binary()\n  def rq_to_sql!(rq_json) do\n    case rq_to_sql(rq_json) do\n      {:ok, result} -> result\n      {:error, reason} -> raise PRQL.PRQLError, reason\n    end\n  end\nend\n"
  },
  {
    "path": "prqlc/bindings/elixir/mix.exs",
    "content": "# Testing Claude commit capability\ndefmodule PRQL.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :prql,\n      version: \"0.1.0\",\n      elixir: \"~> 1.15\",\n      deps: deps(),\n      name: \"PRQL\",\n      source_url: \"https://github.com/PRQL/prql\",\n      homepage_url: \"https://prql-lang.org/\",\n      docs: [\n        # The main page in the docs\n        main: \"readme\",\n        logo: \"../website/static/img/icon.svg\",\n        extras: [\"README.md\"]\n      ]\n    ]\n  end\n\n  # Run \"mix help compile.app\" to learn about applications.\n  def application do\n    [\n      extra_applications: [:logger]\n    ]\n  end\n\n  # Run \"mix help deps\" to learn about dependencies.\n  defp deps do\n    [\n      {:rustler, \"~> 0.37.0\"},\n      {:ex_doc, \"~> 0.21\", only: :dev, runtime: false}\n    ]\n  end\nend\n"
  },
  {
    "path": "prqlc/bindings/elixir/native/prql/.cargo/config.toml",
    "content": "# Note that this doesn't apply when compiling from the workspace root\n\n# (also it seems to work at first glance without this? but it's referenced at\n# https://github.com/philss/rustler_precompiled/blob/main/PRECOMPILATION_GUIDE.md,\n# and I don't know enough to remove it\n[target.'cfg(target_os = \"macos\")']\nrustflags = [\"-C\", \"link-arg=-undefined\", \"-C\", \"link-arg=dynamic_lookup\"]\n"
  },
  {
    "path": "prqlc/bindings/elixir/native/prql/Cargo.toml",
    "content": "[package]\nauthors = [\"Kasun Vithanage <kasvith.me@gmail.com>\"]\ndescription = \"Elixir NIF bindings for prqlc\"\nname = \"prql\"\npublish = false\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nversion.workspace = true\n\n[lib]\nbench = false\ncrate-type = [\"cdylib\"]\ndoc = false\ndoctest = false\nname = \"prql\"\npath = \"src/lib.rs\"\ntest = false\n\n[target.'cfg(not(any(target_family=\"wasm\")))'.dependencies]\nprqlc = { path = \"../../../../prqlc\", default-features = false, version = \"0.13.12\" }\nrustler = \"0.37.0\"\n"
  },
  {
    "path": "prqlc/bindings/elixir/native/prql/README.md",
    "content": "# NIF for Elixir.PRQL\n\n## To build the NIF module:\n\n- Your NIF will now build along with your project.\n- NIFs are loaded into `PRQL.Native` module in Elixir(`lib/prql/native.ex`)\n"
  },
  {
    "path": "prqlc/bindings/elixir/native/prql/src/lib.rs",
    "content": "// These bindings aren't relevant on wasm\n#![cfg(not(target_family = \"wasm\"))]\n// TODO: unclear why we need this `allow`; it's required in `CompileOptions`,\n// likely because of the `NifStruct` derive.\n#![allow(clippy::needless_borrow)]\n\nuse std::default::Default;\n\nuse rustler::{Atom, NifResult, NifStruct, NifTuple};\n\nmod atoms {\n    rustler::atoms! {\n      ok,\n      error,\n\n      // dialects\n      ansi,\n      bigquery,\n      clickhouse,\n      glaredb,\n      generic,\n      mssql,\n      mysql,\n      postgres,\n      redshift,\n      sqlite,\n      snowflake\n    }\n}\n\n/// Convert a `Result` from PRQL into a tuple in elixir `{:ok, binary()} | {:error, binary()}`\nfn to_result_tuple(result: Result<String, prqlc::ErrorMessages>) -> NifResult<Response> {\n    match result {\n        Ok(sql) => Ok(Response {\n            status: atoms::ok(),\n            result: sql,\n        }),\n        Err(e) => Ok(Response {\n            status: atoms::error(),\n            result: e.to_json(),\n        }),\n    }\n}\n\n/// Get the target from an atom. By default `Generic` SQL dialect will be used\nfn target_from_atom(a: Atom) -> prqlc::Target {\n    use prqlc::sql::Dialect::*;\n\n    prqlc::Target::Sql(Some(if a == atoms::ansi() {\n        Ansi\n    } else if a == atoms::bigquery() {\n        BigQuery\n    } else if a == atoms::clickhouse() {\n        ClickHouse\n    } else if a == atoms::generic() {\n        Generic\n    } else if a == atoms::glaredb() {\n        GlareDb\n    } else if a == atoms::mssql() {\n        MsSql\n    } else if a == atoms::mysql() {\n        MySql\n    } else if a == atoms::postgres() {\n        Postgres\n    } else if a == atoms::redshift() {\n        Redshift\n    } else if a == atoms::sqlite() {\n        SQLite\n    } else if a == atoms::snowflake() {\n        Snowflake\n    } else {\n        Generic\n    }))\n}\n\nimpl From<CompileOptions> for prqlc::Options {\n    /// Get `prqlc::Options` options from `CompileOptions`\n    fn from(o: CompileOptions) -> Self {\n        prqlc::Options {\n            format: o.format,\n            target: target_from_atom(o.target),\n            signature_comment: o.signature_comment,\n            display: prqlc::DisplayOptions::Plain,\n            ..Default::default()\n        }\n    }\n}\n\n#[derive(Clone, NifStruct, Debug)]\n#[module = \"PRQL.Native.CompileOptions\"]\npub struct CompileOptions {\n    /// Pass generated SQL string through a formatter that splits it\n    /// into multiple lines and prettifies indentation and spacing.\n    ///\n    /// Defaults to true.\n    pub format: bool,\n\n    /// Target dialect to compile to.\n    ///\n    /// This is only changes the output for a relatively small subset of\n    /// features.\n    ///\n    /// If something does not work in a specific dialect, please raise in a\n    /// GitHub issue.\n    ///\n    /// If `None` is used, the `target` argument from the query header is used.\n    /// If it does not exist, [`prqlc::sql::Dialect::Generic`] is used.\n    pub target: Atom,\n\n    /// Emits the compiler signature as a comment after generated SQL\n    ///\n    /// Defaults to true.\n    pub signature_comment: bool,\n}\n\n#[derive(NifTuple)]\npub struct Response {\n    /// status atom `:ok` or `:error`\n    status: Atom,\n\n    /// result string\n    result: String,\n}\n\n#[rustler::nif]\n/// compile a prql query into sql\npub fn compile(prql_query: &str, options: CompileOptions) -> NifResult<Response> {\n    to_result_tuple(prqlc::compile(prql_query, &options.into()))\n}\n\n#[rustler::nif]\n/// convert a prql query into PL AST\npub fn prql_to_pl(prql_query: &str) -> NifResult<Response> {\n    to_result_tuple(\n        Ok(prql_query)\n            .and_then(prqlc::prql_to_pl)\n            .and_then(|x| prqlc::json::from_pl(&x)),\n    )\n}\n\n#[rustler::nif]\n/// Convert PL AST into RQ\npub fn pl_to_rq(pl_json: &str) -> NifResult<Response> {\n    to_result_tuple(\n        Ok(pl_json)\n            .and_then(prqlc::json::to_pl)\n            .and_then(prqlc::pl_to_rq)\n            .and_then(|x| prqlc::json::from_rq(&x)),\n    )\n}\n\n#[rustler::nif]\n/// Convert RQ to SQL\npub fn rq_to_sql(rq_json: &str) -> NifResult<Response> {\n    to_result_tuple(\n        Ok(rq_json)\n            .and_then(prqlc::json::to_rq)\n            // Currently just using default options here; probably should pass\n            // an argument from this func.\n            .and_then(|x| prqlc::rq_to_sql(x, &prqlc::Options::default())),\n    )\n}\n\nrustler::init!(\"Elixir.PRQL.Native\");\n"
  },
  {
    "path": "prqlc/bindings/elixir/test/prql_test.exs",
    "content": "defmodule PRQLTest do\n  use ExUnit.Case\n  doctest PRQL\n\n  @compile_opts [signature_comment: false]\n\n  test \"compiles PRQL\" do\n    prql_query = \"from customers\"\n\n    assert PRQL.compile(prql_query, @compile_opts) ==\n             {:ok,\n              \"\"\"\n              SELECT\n                *\n              FROM\n                customers\n              \"\"\"}\n  end\n\n  test \"return errors on invalid query\" do\n    {:ok, expected_json} =\n      Jason.decode(~S\"\"\"\n      {\n        \"inner\": [\n          {\n            \"kind\": \"Error\",\n            \"code\": null,\n            \"reason\": \"Unknown name `invalid`\",\n            \"hints\": [],\n            \"span\": \"1:0-7\",\n            \"display\": \"Error: \\n   ╭─[ :1:1 ]\\n   │\\n 1 │ invalid\\n   │ ───┬───  \\n   │    ╰───── Unknown name `invalid`\\n───╯\\n\",\n            \"location\": {\n              \"start\": [0, 0],\n              \"end\": [0, 7]\n            }\n          }\n        ]\n      }\n      \"\"\")\n\n    result = PRQL.compile(\"invalid\", @compile_opts)\n\n    case result do\n      {:error, error_string} ->\n        {:ok, error_json} = Jason.decode(error_string)\n        assert error_json == expected_json\n\n      _ ->\n        flunk(\"Expected an error tuple\")\n    end\n  end\nend\n"
  },
  {
    "path": "prqlc/bindings/elixir/test/test_helper.exs",
    "content": "ExUnit.start()\n"
  },
  {
    "path": "prqlc/bindings/java/.gitignore",
    "content": "target/\n\n**/libprql_java*\n**/prql_java.dll\n"
  },
  {
    "path": "prqlc/bindings/java/Cargo.toml",
    "content": "[package]\nname = \"prql-java\"\npublish = false\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nversion.workspace = true\n\n[lib]\nbench = false\ncrate-type = [\"cdylib\"]\ndoc = false\ndoctest = false\ntest = false\n\n[dependencies]\njni = \"0.21.1\"\nprqlc = {path = \"../../prqlc\", default-features = false}\n\n[package.metadata.release]\ntag-name = \"{{version}}\"\ntag-prefix = \"\"\n"
  },
  {
    "path": "prqlc/bindings/java/DEVELOPMENT.md",
    "content": "# development description for prql-java module\n\n---\n\n## Implementation\n\nWe implement Rust bindings to Java with\n[JNI](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/).\n\nFirst, define a native method --\n`public static native String toSql(String query, String target, boolean format, boolean signature)`\nfor PrqlCompiler, `toJson` is same.\n\nAnd then implement it in Rust with this\n[crate](https://docs.rs/jni/latest/jni/).\n\n## Build\n\nFor ease of use to users, we need pre-build dynamic libs for different\nplatforms. This process is combined into the build of Java module.\n\nWe use [Maven](https://maven.apache.org/) to build the Java library. To add the\nRust cross compilation into the Maven build process, we add the following XML\nsegment to the `pom.xml`:\n\n```xml\n<plugin>\n    <artifactId>exec-maven-plugin</artifactId>\n    <groupId>org.codehaus.mojo</groupId>\n    <version>1.6.0</version>\n    <executions>\n        <execution>\n            <id>Build for release</id>\n            <phase>generate-resources</phase>\n            <goals>\n                <goal>exec</goal>\n            </goals>\n            <configuration>\n                <executable>../cross.sh</executable>\n                <arguments>\n                    <argument>${project.basedir}/../</argument>\n                </arguments>\n            </configuration>\n        </execution>\n    </executions>\n</plugin>\n```\n\nWhen we build, it will execute the `cross.sh` script to get all the Rust\ncdylibs. This process is time consuming.\n\nAs to cross compilation toolchains, we use\n[cross](https://github.com/cross-rs/cross).\n\n## Publish (for maintainer)\n\nTo publish the Java library to Maven public repo, project maintainer need first\nregister a project in the Maven Nexus repository, by the doc:\n<https://central.sonatype.org/publish/publish-guide/>.\n\nAnd then, we can release our artifact in the `release` workflow. The action we\nused is\n[action-maven-publish](https://github.com/marketplace/actions/action-maven-publish).\nProject maintainer has to configure some personal information, those used in the\nfirst step, by the action's doc, such as `nexus_username`, `nexus_password`,\n`gpg_private_key`, `gpg_passphrase`.\n"
  },
  {
    "path": "prqlc/bindings/java/README.md",
    "content": "# prql-java\n\n`prql-java` offers Java bindings to the `prqlc` Rust library. It exposes a Java\nnative method `public static native String toSql(String query)`.\n\nIt's still at an early stage, and currently requires compiling locally, and\nisn't published to Maven. Contributions are welcome.\n\n## Installation\n\n```xml\n<dependency>\n    <groupId>org.prqllang</groupId>\n    <artifactId>prql-java</artifactId>\n    <version>${PRQL_VERSION}</version>\n</dependency>\n```\n\n## Usage\n\n```java\nimport org.prqllang.prql4j.PrqlCompiler;\n\nclass Main {\n    public static void main(String[] args) {\n        String sql = PrqlCompiler.toSql(\"from table\");\n        System.out.println(sql);\n    }\n}\n```\n"
  },
  {
    "path": "prqlc/bindings/java/cross.sh",
    "content": "#!/bin/bash\n\nPRQL_JAVA_MODULE=$1\necho PRQL_JAVA_MODULE=\"${PRQL_JAVA_MODULE}\"\nCONTEXT_PATH=$(pwd)\necho CONTEXT_PATH=\"${CONTEXT_PATH}\"\ncd \"${PRQL_JAVA_MODULE}\" || exit 1\n\n# install cross\ncargo install cross\n\n# x86_64-unknown-linux-gnu\necho \"compiling for x86_64-unknown-linux-gnu\"\nrustup target add x86_64-unknown-linux-gnu\ncross build --release --target x86_64-unknown-linux-gnu\nls -la ../target/x86_64-unknown-linux-gnu/release\ncp -f ../target/x86_64-unknown-linux-gnu/release/libprql_java.so java/src/main/resources/libprql_java-linux64.so\n\n## x86_64-unknown-linux-musl\n#echo \"compiling for x86_64-unknown-linux-musl\"\n#rustup target add x86_64-unknown-linux-musl\n#cross build --release --target x86_64-unknown-linux-musl\n#ls -la ../target/x86_64-unknown-linux-musl/release\n#cp ../target/x86_64-unknown-linux-musl/release/libprql_java.so java/src/main/resources/libprql_java-linux64-musl.so\n\n## x86_64-apple-darwin\n#echo \"compiling for x86_64-apple-darwin\"\n#rustup target add x86_64-apple-darwin\n#cross build --release --target x86_64-apple-darwin\n#ls -la ../target/x86_64-apple-darwin/release\n#cp ../target/x86_64-apple-darwin/release/libprql_java.dylib java/src/main/resources/libprql_java-osx-x86_64.dylib\n\n# x86_64-pc-windows-gnu\necho \"compiling for x86_64-pc-windows-gnu\"\nrustup target add x86_64-pc-windows-gnu\ncross build --release --target x86_64-pc-windows-gnu\nls -la ../target/x86_64-pc-windows-gnu/release\ncp -f ../target/x86_64-pc-windows-gnu/release/prql_java.dll java/src/main/resources/libprql_java-win64.dll\n\n# aarch64-unknown-linux-gnu\necho \"compiling for aarch64-unknown-linux-gnu\"\nrustup target add aarch64-unknown-linux-gnu\ncross build --release --target aarch64-unknown-linux-gnu\nls -la ../target/x86_64-unknown-linux-gnu/release\ncp -f ../target/x86_64-unknown-linux-gnu/release/libprql_java.so java/src/main/resources/libprql_java-linux-aarch64.so\n\n# aarch64-unknown-linux-musl\n#echo \"compiling for aarch64-unknown-linux-musl\"\n#rustup target add aarch64-unknown-linux-musl\n#cross build --release --target aarch64-unknown-linux-musl\n#ls -la ../target/aarch64-unknown-linux-musl/release\n#cp -f ../target/aarch64-unknown-linux-musl/release/libprql_java.so java/src/main/resources/libprql_java-linux-aarch64-musl.so\n\n## aarch64-apple-darwin\n#echo \"compiling for aarch64-apple-darwin\"\n#rustup target add aarch64-apple-darwin\n#cross build --release --target aarch64-apple-darwin\n#ls -la ../target/x86_64-apple-darwin/release\n#cp -f ../target/x86_64-apple-darwin/release/libprql_java.dylib java/src/main/resources/libprql_java-osx-arm64.dylib\n\ncd \"${CONTEXT_PATH}\" || exit 1\n"
  },
  {
    "path": "prqlc/bindings/java/java/.mvn/wrapper/maven-wrapper.properties",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\ndistributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\n"
  },
  {
    "path": "prqlc/bindings/java/java/build.sh",
    "content": "#!/bin/sh\nset -e\n\n# TODO: use a task file for these build scripts\n\nJAVA_SRC_HOME=$1\nARCH=\"$(uname -m)\"\nKERNEL_NAME=\"$(uname -s)\"\nKERNEL_VERSION=\"$(uname -r)\"\n\necho JAVA_SRC_HOME=\"$JAVA_SRC_HOME\"\n\ncd \"$JAVA_SRC_HOME\"\ncd ../\n\necho Platform Info: \"$ARCH\" \"$KERNEL_NAME\" \"$KERNEL_VERSION\"\n\necho building...\ncargo build --release\necho building successfully\nls -la ../../../target/release\n\nif [ \"$KERNEL_NAME\" = 'Linux' ]; then\n  if [ \"$ARCH\" = 'arm64' ] || [ \"$ARCH\" = 'aarch64' ]; then\n    target='libprql_java-linux-aarch64.so'\n  elif [ \"$ARCH\" = 'x86_64' ]; then\n    target='libprql_java-linux64.so'\n  else\n    target='libprql_java-linux32.so'\n  fi\n  cp -f ../../../target/release/libprql_java.so java/src/test/resources/${target}\nelif [ \"$KERNEL_NAME\" = 'Darwin' ]; then\n  if [ \"$ARCH\" = 'arm64' ] || [ \"$ARCH\" = 'aarch64' ]; then\n    target='libprql_java-osx-arm64.dylib'\n  elif [ \"$ARCH\" = 'x86_64' ]; then\n    target='libprql_java-osx-x86_64.dylib'\n  else\n    echo [ERROR] have not support $ARCH:$$KERNEL_NAME yet\n    exit 1\n  fi\n  cp -f ../../../target/release/libprql_java.dylib java/src/test/resources/${target}\nfi\n\nls -la ./java/src/main/resources\n"
  },
  {
    "path": "prqlc/bindings/java/java/mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Apache Maven Wrapper startup batch script, version 3.1.1\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /usr/local/etc/mavenrc ] ; then\n    . /usr/local/etc/mavenrc\n  fi\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        JAVA_HOME=\"`/usr/libexec/java_home`\"; export JAVA_HOME\n      else\n        JAVA_HOME=\"/Library/Java/Home\"; export JAVA_HOME\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`\\\\unset -f command; \\\\command -v java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  printf '%s' \"$(cd \"$basedir\"; pwd)\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=$(find_maven_basedir \"$(dirname $0)\")\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\nMAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}; export MAVEN_PROJECTBASEDIR\nif [ \"$MVNW_VERBOSE\" = true ]; then\n  echo $MAVEN_PROJECTBASEDIR\nfi\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nif [ -r \"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\" ]; then\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Found .mvn/wrapper/maven-wrapper.jar\"\n    fi\nelse\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ...\"\n    fi\n    if [ -n \"$MVNW_REPOURL\" ]; then\n      wrapperUrl=\"$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\"\n    else\n      wrapperUrl=\"https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\"\n    fi\n    while IFS=\"=\" read key value; do\n      case \"$key\" in (wrapperUrl) wrapperUrl=\"$value\"; break ;;\n      esac\n    done < \"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties\"\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Downloading from: $wrapperUrl\"\n    fi\n    wrapperJarPath=\"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\"\n    if $cygwin; then\n      wrapperJarPath=`cygpath --path --windows \"$wrapperJarPath\"`\n    fi\n\n    if command -v wget > /dev/null; then\n        QUIET=\"--quiet\"\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found wget ... using wget\"\n          QUIET=\"\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            wget $QUIET \"$wrapperUrl\" -O \"$wrapperJarPath\"\n        else\n            wget $QUIET --http-user=\"$MVNW_USERNAME\" --http-password=\"$MVNW_PASSWORD\" \"$wrapperUrl\" -O \"$wrapperJarPath\"\n        fi\n        [ $? -eq 0 ] || rm -f \"$wrapperJarPath\"\n    elif command -v curl > /dev/null; then\n        QUIET=\"--silent\"\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found curl ... using curl\"\n          QUIET=\"\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            curl $QUIET -o \"$wrapperJarPath\" \"$wrapperUrl\" -f -L\n        else\n            curl $QUIET --user \"$MVNW_USERNAME:$MVNW_PASSWORD\" -o \"$wrapperJarPath\" \"$wrapperUrl\" -f -L\n        fi\n        [ $? -eq 0 ] || rm -f \"$wrapperJarPath\"\n    else\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Falling back to using Java to download\"\n        fi\n        javaSource=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        javaClass=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\"\n        # For Cygwin, switch paths to Windows format before running javac\n        if $cygwin; then\n          javaSource=`cygpath --path --windows \"$javaSource\"`\n          javaClass=`cygpath --path --windows \"$javaClass\"`\n        fi\n        if [ -e \"$javaSource\" ]; then\n            if [ ! -e \"$javaClass\" ]; then\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Compiling MavenWrapperDownloader.java ...\"\n                fi\n                # Compiling the Java class\n                (\"$JAVA_HOME/bin/javac\" \"$javaSource\")\n            fi\n            if [ -e \"$javaClass\" ]; then\n                # Running the downloader\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Running MavenWrapperDownloader.java ...\"\n                fi\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$MAVEN_PROJECTBASEDIR\")\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\n# Provide a \"standardized\" way to retrieve the CLI args that will\n# work with both Windows and non-Windows executions.\nMAVEN_CMD_LINE_ARGS=\"$MAVEN_CONFIG $@\"\nexport MAVEN_CMD_LINE_ARGS\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  $MAVEN_DEBUG_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "prqlc/bindings/java/java/mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software Foundation (ASF) under one\n@REM or more contributor license agreements.  See the NOTICE file\n@REM distributed with this work for additional information\n@REM regarding copyright ownership.  The ASF licenses this file\n@REM to you under the Apache License, Version 2.0 (the\n@REM \"License\"); you may not use this file except in compliance\n@REM with the License.  You may obtain a copy of the License at\n@REM\n@REM    http://www.apache.org/licenses/LICENSE-2.0\n@REM\n@REM Unless required by applicable law or agreed to in writing,\n@REM software distributed under the License is distributed on an\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n@REM KIND, either express or implied.  See the License for the\n@REM specific language governing permissions and limitations\n@REM under the License.\n@REM ----------------------------------------------------------------------------\n\n@REM ----------------------------------------------------------------------------\n@REM Apache Maven Wrapper startup batch script, version 3.1.1\n@REM\n@REM Required ENV vars:\n@REM JAVA_HOME - location of a JDK home dir\n@REM\n@REM Optional ENV vars\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\n@REM     e.g. to debug Maven itself, use\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n@REM ----------------------------------------------------------------------------\n\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\n@echo off\n@REM set title of command window\ntitle %0\n@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\n\n@REM set %HOME% to equivalent of $HOME\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\n\n@REM Execute a user defined script before this one\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\nif exist \"%USERPROFILE%\\mavenrc_pre.bat\" call \"%USERPROFILE%\\mavenrc_pre.bat\" %*\nif exist \"%USERPROFILE%\\mavenrc_pre.cmd\" call \"%USERPROFILE%\\mavenrc_pre.cmd\" %*\n:skipRcPre\n\n@setlocal\n\nset ERROR_CODE=0\n\n@REM To isolate internal variables from possible post scripts, we use another setlocal\n@setlocal\n\n@REM ==== START VALIDATION ====\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\n\necho.\necho Error: JAVA_HOME not found in your environment. >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n:OkJHome\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\n\necho.\necho Error: JAVA_HOME is set to an invalid directory. >&2\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n@REM ==== END VALIDATION ====\n\n:init\n\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\n@REM Fallback to current working directory if not found.\n\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\n\nset EXEC_DIR=%CD%\nset WDIR=%EXEC_DIR%\n:findBaseDir\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\ncd ..\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\nset WDIR=%CD%\ngoto findBaseDir\n\n:baseDirFound\nset MAVEN_PROJECTBASEDIR=%WDIR%\ncd \"%EXEC_DIR%\"\ngoto endDetectBaseDir\n\n:baseDirNotFound\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\ncd \"%EXEC_DIR%\"\n\n:endDetectBaseDir\n\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\n\n@setlocal EnableExtensions EnableDelayedExpansion\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\n\n:endReadAdditionalConfig\n\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nset WRAPPER_URL=\"https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\"\n\nFOR /F \"usebackq tokens=1,2 delims==\" %%A IN (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties\") DO (\n    IF \"%%A\"==\"wrapperUrl\" SET WRAPPER_URL=%%B\n)\n\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\nif exist %WRAPPER_JAR% (\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Found %WRAPPER_JAR%\n    )\n) else (\n    if not \"%MVNW_REPOURL%\" == \"\" (\n        SET WRAPPER_URL=\"%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar\"\n    )\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Couldn't find %WRAPPER_JAR%, downloading it ...\n        echo Downloading from: %WRAPPER_URL%\n    )\n\n    powershell -Command \"&{\"^\n\t\t\"$webclient = new-object System.Net.WebClient;\"^\n\t\t\"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {\"^\n\t\t\"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');\"^\n\t\t\"}\"^\n\t\t\"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')\"^\n\t\t\"}\"\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Finished downloading %WRAPPER_JAR%\n    )\n)\n@REM End of extension\n\n@REM Provide a \"standardized\" way to retrieve the CLI args that will\n@REM work with both Windows and non-Windows executions.\nset MAVEN_CMD_LINE_ARGS=%*\n\n%MAVEN_JAVA_EXE% ^\n  %JVM_CONFIG_MAVEN_PROPS% ^\n  %MAVEN_OPTS% ^\n  %MAVEN_DEBUG_OPTS% ^\n  -classpath %WRAPPER_JAR% ^\n  \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" ^\n  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\nif ERRORLEVEL 1 goto error\ngoto end\n\n:error\nset ERROR_CODE=1\n\n:end\n@endlocal & set ERROR_CODE=%ERROR_CODE%\n\nif not \"%MAVEN_SKIP_RC%\"==\"\" goto skipRcPost\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\nif exist \"%USERPROFILE%\\mavenrc_post.bat\" call \"%USERPROFILE%\\mavenrc_post.bat\"\nif exist \"%USERPROFILE%\\mavenrc_post.cmd\" call \"%USERPROFILE%\\mavenrc_post.cmd\"\n:skipRcPost\n\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\nif \"%MAVEN_BATCH_PAUSE%\"==\"on\" pause\n\nif \"%MAVEN_TERMINATE_CMD%\"==\"on\" exit %ERROR_CODE%\n\ncmd /C exit /B %ERROR_CODE%\n"
  },
  {
    "path": "prqlc/bindings/java/java/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>org.prqllang</groupId>\n    <artifactId>prql-java</artifactId>\n    <version>0.5.2</version>\n\n    <name>${project.groupId}:${project.artifactId}</name>\n    <description>prqlc api for java</description>\n    <url>https://github.com/PRQL/prql</url>\n\n    <licenses>\n        <license>\n            <name>The Apache License, Version 2.0</name>\n            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n        </license>\n    </licenses>\n\n    <developers>\n        <developer>\n            <name>PRQL Developers</name>\n            <url>prql@prql-lang.org</url>\n        </developer>\n    </developers>\n\n    <scm>\n        <connection>scm:git:git://github.com/PRQL/prql.git</connection>\n        <developerConnection>scm:git:ssh://github.com:prql/prql.git</developerConnection>\n        <url>https://github.com/PRQL/prql/tree/main</url>\n    </scm>\n\n    <properties>\n        <maven.compiler.target>1.8</maven.compiler.target>\n        <maven.compiler.source>1.8</maven.compiler.source>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <version>4.13.2</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <artifactId>exec-maven-plugin</artifactId>\n                <groupId>org.codehaus.mojo</groupId>\n                <version>1.6.0</version>\n                <executions>\n                    <execution>\n                        <id>Build for tests</id>\n                        <phase>generate-test-resources</phase>\n                        <goals>\n                            <goal>exec</goal>\n                        </goals>\n                        <configuration>\n                            <executable>build.sh</executable>\n                            <arguments>\n                                <argument>${project.basedir}</argument>\n                            </arguments>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <!-- plugins for publishing -->\n            <plugin>\n                <groupId>org.sonatype.plugins</groupId>\n                <artifactId>nexus-staging-maven-plugin</artifactId>\n                <version>1.6.13</version>\n                <extensions>true</extensions>\n                <configuration>\n                    <serverId>ossrh</serverId>\n                    <nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>\n                    <autoReleaseAfterClose>true</autoReleaseAfterClose>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-gpg-plugin</artifactId>\n                <version>1.5</version>\n                <executions>\n                    <execution>\n                        <id>sign-artifacts</id>\n                        <phase>verify</phase>\n                        <goals>\n                            <goal>sign</goal>\n                        </goals>\n                        <configuration>\n                            <gpgArguments>\n                                <arg>--pinentry-mode</arg>\n                                <arg>loopback</arg>\n                            </gpgArguments>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <artifactId>maven-source-plugin</artifactId>\n                <version>3.1.0</version>\n                <executions>\n                    <execution>\n                        <id>attach-source</id>\n                        <phase>compile</phase>\n                        <goals>\n                            <goal>jar-no-fork</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <version>3.0.1</version>\n                <executions>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <profiles>\n        <profile>\n            <id>deploy-snapshot</id>\n            <distributionManagement>\n                <snapshotRepository>\n                    <id>sonatype-nexus-snapshots</id>\n                    <name>Sonatype Nexus Snapshots</name>\n                    <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>\n                </snapshotRepository>\n            </distributionManagement>\n            <build>\n                <plugins>\n                    <plugin>\n                        <artifactId>exec-maven-plugin</artifactId>\n                        <groupId>org.codehaus.mojo</groupId>\n                        <version>1.6.0</version>\n                        <executions>\n                            <execution>\n                                <id>Build for release</id>\n                                <phase>generate-resources</phase>\n                                <goals>\n                                    <goal>exec</goal>\n                                </goals>\n                                <configuration>\n                                    <executable>../cross.sh</executable>\n                                    <arguments>\n                                        <argument>${project.basedir}/../</argument>\n                                    </arguments>\n                                </configuration>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n        <profile>\n            <id>deploy</id>\n            <distributionManagement>\n                <repository>\n                    <id>sonatype-nexus-staging</id>\n                    <name>Nexus Release Repository</name>\n                    <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2</url>\n                </repository>\n            </distributionManagement>\n            <build>\n                <plugins>\n                    <plugin>\n                        <artifactId>exec-maven-plugin</artifactId>\n                        <groupId>org.codehaus.mojo</groupId>\n                        <version>1.6.0</version>\n                        <executions>\n                            <execution>\n                                <id>Build for release</id>\n                                <phase>generate-resources</phase>\n                                <goals>\n                                    <goal>exec</goal>\n                                </goals>\n                                <configuration>\n                                    <executable>../cross.sh</executable>\n                                    <arguments>\n                                        <argument>${project.basedir}/../</argument>\n                                    </arguments>\n                                </configuration>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n\n</project>\n"
  },
  {
    "path": "prqlc/bindings/java/java/src/main/java/org/prql/prql4j/Environment.java",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.\n// Original License: https://github.com/facebook/rocksdb/blob/main/LICENSE.Apache\npackage org.prql.prql4j;\n\nimport java.io.IOException;\n\npublic class Environment {\n  private static final String OS = System.getProperty(\"os.name\").toLowerCase();\n  private static final String ARCH = System.getProperty(\"os.arch\").toLowerCase();\n  private static boolean MUSL_LIBC;\n\n  static {\n    try {\n      final Process p = new ProcessBuilder(\"/usr/bin/env\", \"sh\", \"-c\", \"ldd /usr/bin/env | grep -q musl\").start();\n      MUSL_LIBC = p.waitFor() == 0;\n    } catch (final IOException | InterruptedException e) {\n      MUSL_LIBC = false;\n    }\n  }\n\n  public static boolean isAarch64() {\n    return ARCH.contains(\"aarch64\");\n  }\n\n  public static boolean isPowerPC() {\n    return ARCH.contains(\"ppc\");\n  }\n\n  public static boolean isS390x() {\n    return ARCH.contains(\"s390x\");\n  }\n\n  public static boolean isWindows() {\n    return (OS.contains(\"win\"));\n  }\n\n  public static boolean isFreeBSD() {\n    return (OS.contains(\"freebsd\"));\n  }\n\n  public static boolean isMac() {\n    return (OS.contains(\"mac\"));\n  }\n\n  public static boolean isAix() {\n    return OS.contains(\"aix\");\n  }\n\n  public static boolean isUnix() {\n    return OS.contains(\"nix\") ||\n        OS.contains(\"nux\");\n  }\n\n  public static boolean isMuslLibc() {\n    return MUSL_LIBC;\n  }\n\n  public static boolean isSolaris() {\n    return OS.contains(\"sunos\");\n  }\n\n  public static boolean isOpenBSD() {\n    return (OS.contains(\"openbsd\"));\n  }\n\n  public static boolean is64Bit() {\n    if (ARCH.contains(\"sparcv9\")) {\n      return true;\n    }\n    return (ARCH.indexOf(\"64\") > 0);\n  }\n\n  public static String getSharedLibraryName(final String name) {\n    return name;\n  }\n\n  public static String getSharedLibraryFileName(final String name) {\n    return appendLibOsSuffix(\"lib\" + getSharedLibraryName(name));\n  }\n\n  /**\n   * Get the name of the libc implementation\n   *\n   * @return the name of the implementation,\n   *    or null if the default for that platform (e.g. glibc on Linux).\n   */\n  public static /* @Nullable */ String getLibcName() {\n    if (isMuslLibc()) {\n      return \"musl\";\n    } else {\n      return null;\n    }\n  }\n\n  private static String getLibcPostfix() {\n    final String libcName = getLibcName();\n    if (libcName == null) {\n      return \"\";\n    }\n    return \"-\" + libcName;\n  }\n\n  public static String getJniLibraryName(final String name) {\n    if (isUnix()) {\n      final String arch = is64Bit() ? \"64\" : \"32\";\n      if (isPowerPC() || isAarch64()) {\n        return String.format(\"%s-linux-%s%s\", name, ARCH, getLibcPostfix());\n      } else if (isS390x()) {\n        return String.format(\"%s-linux-%s\", name, ARCH);\n      } else {\n        return String.format(\"%s-linux%s%s\", name, arch, getLibcPostfix());\n      }\n    } else if (isMac()) {\n      if (is64Bit()) {\n        final String arch;\n        if (isAarch64()) {\n          arch = \"arm64\";\n        } else {\n          arch = \"x86_64\";\n        }\n        return String.format(\"%s-osx-%s\", name, arch);\n      } else {\n        return String.format(\"%s-osx\", name);\n      }\n    } else if (isFreeBSD()) {\n      return String.format(\"%s-freebsd%s\", name, is64Bit() ? \"64\" : \"32\");\n    } else if (isAix() && is64Bit()) {\n      return String.format(\"%s-aix64\", name);\n    } else if (isSolaris()) {\n      final String arch = is64Bit() ? \"64\" : \"32\";\n      return String.format(\"%s-solaris%s\", name, arch);\n    } else if (isWindows() && is64Bit()) {\n      return String.format(\"%s-win64\", name);\n    } else if (isOpenBSD()) {\n      return String.format(\"%s-openbsd%s\", name, is64Bit() ? \"64\" : \"32\");\n    }\n\n    throw new UnsupportedOperationException(String.format(\"Cannot determine JNI library name for ARCH='%s' OS='%s' name='%s'\", ARCH, OS, name));\n  }\n\n  public static /*@Nullable*/ String getFallbackJniLibraryName(final String name) {\n    if (isMac() && is64Bit()) {\n      return String.format(\"%s-osx\", name);\n    }\n    return null;\n  }\n\n  public static String getJniLibraryFileName(final String name) {\n    return appendLibOsSuffix(\"lib\" + getJniLibraryName(name));\n  }\n\n  public static /*@Nullable*/ String getFallbackJniLibraryFileName(final String name) {\n    final String fallbackJniLibraryName = getFallbackJniLibraryName(name);\n    if (fallbackJniLibraryName == null) {\n      return null;\n    }\n    return appendLibOsSuffix(\"lib\" + fallbackJniLibraryName);\n  }\n\n  private static String appendLibOsSuffix(final String libraryFileName) {\n    if (isUnix() || isAix() || isSolaris() || isFreeBSD() || isOpenBSD()) {\n      return libraryFileName + \".so\";\n    } else if (isMac()) {\n      return libraryFileName + \".dylib\";\n    } else if (isWindows()) {\n      return libraryFileName + \".dll\";\n    }\n    throw new UnsupportedOperationException();\n  }\n\n  public static String getJniLibraryExtension() {\n    if (isWindows()) {\n      return \".dll\";\n    }\n    return (isMac()) ? \".dylib\" : \".so\";\n  }\n}\n"
  },
  {
    "path": "prqlc/bindings/java/java/src/main/java/org/prql/prql4j/NativeLibraryLoader.java",
    "content": "// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.\n// Original License: https://github.com/facebook/rocksdb/blob/main/LICENSE.Apache\npackage org.prql.prql4j;\n\nimport java.io.*;\nimport java.nio.file.Files;\nimport java.nio.file.StandardCopyOption;\n\npublic class NativeLibraryLoader {\n    //singleton\n    private static final NativeLibraryLoader instance = new NativeLibraryLoader();\n    private static boolean initialized = false;\n\n    private static final String sharedLibraryName = Environment.getSharedLibraryName(\"prql_java\");\n    private static final String jniLibraryName = Environment.getJniLibraryName(\"prql_java\");\n    private static final /* @Nullable */ String fallbackJniLibraryName =\n            Environment.getFallbackJniLibraryName(\"prql_java\");\n    private static final String jniLibraryFileName = Environment.getJniLibraryFileName(\"prql_java\");\n    private static final /* @Nullable */ String fallbackJniLibraryFileName =\n            Environment.getFallbackJniLibraryFileName(\"prql_java\");\n    private static final String tempFilePrefix = \"libprql_javajni\";\n    private static final String tempFileSuffix = Environment.getJniLibraryExtension();\n\n    public static NativeLibraryLoader getInstance() {\n        return instance;\n    }\n\n    public synchronized void loadLibrary(final String tmpDir) throws IOException {\n        try {\n            // try dynamic library\n            System.loadLibrary(sharedLibraryName);\n            return;\n        } catch (final UnsatisfiedLinkError ule) {\n            // ignore - try from static library\n        }\n\n        try {\n            // try static library\n            System.loadLibrary(jniLibraryName);\n            return;\n        } catch (final UnsatisfiedLinkError ule) {\n            // ignore - then try static library fallback or from jar\n        }\n\n        if (fallbackJniLibraryName != null) {\n            try {\n                // try static library fallback\n                System.loadLibrary(fallbackJniLibraryName);\n                return;\n            } catch (final UnsatisfiedLinkError ule) {\n                // ignore - then try from jar\n            }\n        }\n\n        // try jar\n        loadLibraryFromJar(tmpDir);\n    }\n\n    void loadLibraryFromJar(final String tmpDir)\n            throws IOException {\n        if (!initialized) {\n            System.load(loadLibraryFromJarToTemp(tmpDir).getAbsolutePath());\n            initialized = true;\n        }\n    }\n\n    File loadLibraryFromJarToTemp(final String tmpDir)\n            throws IOException {\n        InputStream is = null;\n        try {\n            // attempt to look up the static library in the jar file\n            String libraryFileName = jniLibraryFileName;\n            is = getClass().getClassLoader().getResourceAsStream(libraryFileName);\n\n            if (is == null) {\n                // is there a fallback we can try\n                if (fallbackJniLibraryFileName == null) {\n                    throw new RuntimeException(libraryFileName + \" was not found inside JAR.\");\n                }\n\n                // attempt to look up the fallback static library in the jar file\n                libraryFileName = fallbackJniLibraryFileName;\n                is = getClass().getClassLoader().getResourceAsStream(libraryFileName);\n                if (is == null) {\n                    throw new RuntimeException(libraryFileName + \" was not found inside JAR.\");\n                }\n            }\n\n            // create a temporary file to copy the library to\n            final File temp;\n            if (tmpDir == null || tmpDir.isEmpty()) {\n                temp = File.createTempFile(tempFilePrefix, tempFileSuffix);\n            } else {\n                final File parentDir = new File(tmpDir);\n                if (!parentDir.exists()) {\n                    throw new RuntimeException(\n                            \"Directory: \" + parentDir.getAbsolutePath() + \" does not exist!\");\n                }\n                temp = new File(parentDir, libraryFileName);\n                if (temp.exists() && !temp.delete()) {\n                    throw new RuntimeException(\n                            \"File: \" + temp.getAbsolutePath() + \" already exists and cannot be removed.\");\n                }\n                if (!temp.createNewFile()) {\n                    throw new RuntimeException(\"File: \" + temp.getAbsolutePath() + \" could not be created.\");\n                }\n            }\n            if (!temp.exists()) {\n                throw new RuntimeException(\"File \" + temp.getAbsolutePath() + \" does not exist.\");\n            } else {\n                temp.deleteOnExit();\n            }\n\n            // copy the library from the Jar file to the temp destination\n            Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING);\n\n            // return the temporary library file\n            return temp;\n\n        } finally {\n            if (is != null) {\n                is.close();\n            }\n        }\n    }\n\n    private NativeLibraryLoader() {\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/java/java/src/main/java/org/prql/prql4j/PrqlCompiler.java",
    "content": "package org.prql.prql4j;\n\nimport java.io.IOException;\n\npublic class PrqlCompiler {\n\n    /**\n     * compile PRQL to SQL\n     * @param query PRQL query\n     * @param target target dialect, such as sql.mysql etc. Please refer <a href=\"https://github.com/PRQL/prql/blob/main/web/book/src/project/target.md\">PRQL Target and Version</a>\n     * @param format format SQL or not\n     * @param signature comment signature or not\n     * @return SQL\n     * @throws Exception PRQL compile exception\n     */\n    public static native String toSql(String query, String target, boolean format, boolean signature) throws Exception;\n    public static native String toJson(String query) throws Exception;\n    public static native String format(String query) throws Exception;\n\n    static {\n        try {\n            NativeLibraryLoader.getInstance().loadLibrary(null);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/java/java/src/main/resources/.gitkeep",
    "content": ""
  },
  {
    "path": "prqlc/bindings/java/java/src/test/java/org/prql/prql4j/PrqlCompilerTest.java",
    "content": "package org.prql.prql4j;\n\nimport org.junit.Test;\n\npublic class PrqlCompilerTest {\n    @Test\n    public void compile() throws Exception {\n        String found = PrqlCompiler.toSql(\"from my_table\", \"sql.mysql\", true, true);\n\n        // remove signature\n        found = found.substring(0, found.indexOf(\"\\n\\n--\"));\n\n        String expected = \"SELECT\\n\" +\n                \"  *\\n\" +\n                \"FROM\\n\" +\n                \"  my_table\";\n        assert expected.equalsIgnoreCase(found);\n    }\n\n    @Test(expected = Exception.class)\n    public void compileWithError() throws Exception {\n       PrqlCompiler.toSql(\"from table | filter id >> 1\", \"sql.mysql\", true, true);\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/java/java/src/test/resources/.gitkeep",
    "content": ""
  },
  {
    "path": "prqlc/bindings/java/src/lib.rs",
    "content": "use std::str::FromStr;\n\nuse jni::objects::{JClass, JString};\nuse jni::sys::{jboolean, jstring};\nuse jni::JNIEnv;\nuse prqlc::{json, pl_to_prql, prql_to_pl, ErrorMessages, Options, Target};\n\n#[no_mangle]\n#[allow(non_snake_case)]\npub extern \"system\" fn Java_org_prql_prql4j_PrqlCompiler_toSql(\n    mut env: JNIEnv,\n    _class: JClass,\n    query: JString,\n    target: JString,\n    format: jboolean,\n    signature: jboolean,\n) -> jstring {\n    let prql_query: String = env\n        .get_string(&query)\n        .expect(\"Couldn't get java string!\")\n        .into();\n    let target_str: String = env\n        .get_string(&target)\n        .expect(\"Couldn't get java string\")\n        .into();\n    let prql_dialect: Target = Target::from_str(&target_str).unwrap_or(Target::Sql(None));\n    let opt = Options {\n        format: format != 0,\n        target: prql_dialect,\n        signature_comment: signature != 0,\n        // TODO: add support for `display`\n        ..Default::default()\n    };\n    let result = prqlc::compile(&prql_query, &opt);\n    java_string_with_exception(result, &mut env)\n}\n\n#[no_mangle]\n#[allow(non_snake_case)]\npub extern \"system\" fn Java_org_prql_prql4j_PrqlCompiler_format(\n    mut env: JNIEnv,\n    _class: JClass,\n    query: JString,\n) -> jstring {\n    let prql_query: String = env\n        .get_string(&query)\n        .expect(\"Couldn't get java string!\")\n        .into();\n    let result = prql_to_pl(&prql_query).and_then(|x| pl_to_prql(&x));\n    java_string_with_exception(result, &mut env)\n}\n\n#[no_mangle]\n#[allow(non_snake_case)]\npub extern \"system\" fn Java_org_prql_prql4j_PrqlCompiler_toJson(\n    mut env: JNIEnv,\n    _class: JClass,\n    query: JString,\n) -> jstring {\n    let prql_query: String = env\n        .get_string(&query)\n        .expect(\"Couldn't get java string!\")\n        .into();\n    let result = prql_to_pl(&prql_query).and_then(|x| json::from_pl(&x));\n    java_string_with_exception(result, &mut env)\n}\n\nfn java_string_with_exception(result: Result<String, ErrorMessages>, env: &mut JNIEnv) -> jstring {\n    if let Ok(text) = result {\n        env.new_string(text)\n            .expect(\"Couldn't create java string!\")\n            .into_raw()\n    } else {\n        let exception = env.find_class(\"java/lang/Exception\").unwrap();\n        if let Err(e) = env.throw_new(exception, result.err().unwrap().to_string()) {\n            println!(\"Error throwing exception: {e:?}\");\n        }\n        std::ptr::null_mut() as jstring\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/js/Cargo.toml",
    "content": "[package]\ndescription = \"Javascript bindings for prqlc\"\nname = \"prqlc-js\"\npublish = false\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nversion.workspace = true\n\n[lib]\nbench = false\ncrate-type = [\"cdylib\", \"rlib\"]\ndoc = false\ndoctest = false\ntest = false\n\n[features]\ndefault = [\"console_error_panic_hook\"]\n\n[target.'cfg(target_family=\"wasm\")'.dependencies]\nprqlc = {path = \"../../prqlc\", default-features = false}\nwasm-bindgen = \"0.2.114\"\n\n# The `console_error_panic_hook` crate provides better debugging of panics by\n# logging them with `console.error`. While the docs state that \"This is great\n# for development, but requires all the `std::fmt` and `std::panicking`\n# infrastructure, so isn't great for code size when deploying.\", on testing with\n# `--no-default-features`,\n# `prql/target/wasm32-unknown-unknown/release/prqlc_js.wasm` is 7.416MB vs\n# 7.408MB. Maybe because we're already including lots of that with other library\n# features? Even running `wasm-opt prqlc_js.wasm` makes similarly sized files of\n# 5.7M. Feel free to try removing this as part of reducing code size (would be\n# good to have a much smaller code size...).\nconsole_error_panic_hook = {version = \"0.1.7\", optional = true}\n\n[target.'cfg(target_family=\"wasm\")'.dev-dependencies]\nwasm-bindgen-test = \"0.3.64\"\n\n[package.metadata.cargo-udeps.ignore]\ndevelopment = [\"wasm-bindgen-test\"]\n\n[package.metadata.release]\ntag-name = \"{{version}}\"\ntag-prefix = \"\"\n\n[[package.metadata.release.pre-release-replacements]]\nexactly = 1\nfile = \"package.json\"\nreplace = '$1\"{{version}}\"'\nsearch = '(  \"version\": )\"(\\d+\\.\\d+\\.\\d+)\"'\n\n[[package.metadata.release.pre-release-replacements]]\nexactly = 2\nfile = \"package-lock.json\"\nreplace = '$1\"{{version}}\"'\nsearch = '''\n(\\s+\"prqlc\",\n\\s+\"version\": )\"[\\d\\.]+\"'''\n\n[[package.metadata.release.pre-release-replacements]]\nexactly = 1\nfile = \"../../../web/playground/package.json\"\nreplace = '$1\"{{version}}\"'\nsearch = '(  \"version\": )\"(\\d+\\.\\d+\\.\\d+)\"'\n\n[[package.metadata.release.pre-release-replacements]]\nexactly = 2\nfile = \"../../../web/playground/package-lock.json\"\nreplace = '$1\"{{version}}\"'\nsearch = '''\n(\\s+\"prql-playground\",\n\\s+\"version\": )\"[\\d\\.]+\"'''\n\n[[package.metadata.release.pre-release-replacements]]\nexactly = 1\nfile = \"../../../web/playground/package-lock.json\"\nreplace = '$1\"{{version}}\"'\nsearch = '''\n(\".*/prqlc/bindings/js\": \\{\n\\s+\"name\":.*\n\\s+\"version\": )\"[\\d\\.]+\"'''\n"
  },
  {
    "path": "prqlc/bindings/js/README.md",
    "content": "# prqlc-js\n\nJavaScript bindings for [`prqlc`](https://github.com/PRQL/prql/).\n\n## Installation\n\n```sh\nnpm install prqlc\n```\n\n## Usage\n\nCurrently these functions are exposed\n\n```typescript\nfunction compile(prql_query: string, options?: CompileOptions): string;\n\nfunction prql_to_pl(prql_query: string): string;\n\nfunction pl_to_prql(pl_json: string): string;\n\nfunction pl_to_rq(pl_json: string): string;\n\nfunction rq_to_sql(rq_json: string): string;\n```\n\n### From Node.js\n\nDirect usage\n\n```javascript\nconst prqlc = require(\"prqlc\");\n\nconst sql = prqlc.compile(`from employees | select first_name`);\nconsole.log(sql);\n```\n\nOptions\n\n```javascript\nconst opts = new prqlc.CompileOptions();\nopts.target = \"sql.mssql\";\nopts.format = false;\nopts.signature_comment = false;\n\nconst sql = prqlc.compile(`from employees | take 10`, opts);\nconsole.log(sql);\n```\n\nTemplate literal\n\n```javascript\nconst prqlc = require(\"prqlc\");\nconst prql = (string) => prqlc.compile(string[0] || \"\");\n\nconst sql = prql`from employees | select first_name`;\nconsole.log(sql);\n```\n\nTemplate literal with newlines\n\n```javascript\nconst prqlc = require(\"prqlc\");\nconst prql = (string) => prqlc.compile(string[0] || \"\");\n\nconst sql = prql`\n    from employees\n    select first_name\n`;\nconsole.log(sql);\n```\n\n### From a browser\n\n```html\n<html>\n  <head>\n    <script type=\"module\">\n      import init, { compile } from \"./dist/web/prql_js.js\";\n      await init();\n\n      const sql = compile(\"from employees | select first_name\");\n      console.log(sql);\n    </script>\n  </head>\n\n  <body></body>\n</html>\n```\n\n### From a framework or a bundler\n\n```typescript\nimport compile from \"prqlc/dist/bundler\";\n\nconst sql = compile(`from employees | select first_name`);\nconsole.log(sql);\n```\n\n## Errors\n\nErrors are returned as following object, serialized as a JSON array:\n\n```typescript\ninterface ErrorMessage {\n  /// Message kind. Currently only Error is implemented.\n  kind: \"Error\" | \"Warning\" | \"Lint\";\n  /// Machine-readable identifier of the error\n  code: string | null;\n  /// Plain text of the error\n  reason: string;\n  /// A list of suggestions of how to fix the error\n  hint: string | null;\n  /// Character offset of error origin within a source file\n  span: [number, number] | null;\n\n  /// Annotated code, containing cause and hints.\n  display: string | null;\n  /// Line and column number of error origin within a source file\n  location: SourceLocation | null;\n}\n\n/// Location within the source file.\n/// Tuples contain:\n/// - line number (0-based),\n/// - column number within that line (0-based),\ninterface SourceLocation {\n  start: [number, number];\n\n  end: [number, number];\n}\n```\n\nThese errors can be caught as such:\n\n```javascript\ntry {\n  const sql = prqlJs.compile(`from employees | foo first_name`);\n} catch (error) {\n  const errorMessages = JSON.parse(error.message).inner;\n\n  console.log(errorMessages[0].display);\n  console.log(errorMessages[0].location);\n}\n```\n\n## Development\n\nBuild:\n\n```sh\nnpm run build\n```\n\nThis builds Node, bundler and web packages in the `dist` path.\n\nTest:\n\n```sh\nnpm test\n```\n\nBy default the `wasm` binaries are optimized on each run, even if the underlying\ncode hasn't changed, which can be slow. For a lower-latency dev loop, pass\n`--profile=dev` to `npm install` for a faster, less optimized build.\n\n```sh\nnpm install prqlc --profile=dev\n```\n\n## Notes\n\n- This uses [`wasm-pack`](https://rustwasm.github.io/docs/wasm-pack/) to\n  generate bindings[^1].\n- We've added an `npm` layer on top of the usual approach of just using\n  `wasm-pack`, so we can distribute a single package with targets of `node`,\n  `bundler` and `no-modules` — somewhat inverting the approach recommended by\n  `wasm-pack`. The build instruction goes in a `build` script, rather than a\n  `pack` script.\n\n[^1]:\n    Though we would be very open to other approaches, given wasm-pack does not\n    seem maintained, and we're eliding many of its features to build for three\n    targets. See <https://github.com/PRQL/prql/issues/1836> for more details.\n"
  },
  {
    "path": "prqlc/bindings/js/package.json",
    "content": "{\n  \"browser\": \"dist/web/prqlc_js.js\",\n  \"description\": \"JavaScript bindings for prqlc\",\n  \"devDependencies\": {\n    \"chai\": \"^6.0.1\",\n    \"mocha\": \"^11.0.1\",\n    \"cross-env\": \"^10.0.0\"\n  },\n  \"files\": [\n    \"dist/**/*\",\n    \"package.json\"\n  ],\n  \"license\": \"Apache-2.0\",\n  \"author\": \"PRQL team\",\n  \"main\": \"dist/node/prqlc_js.js\",\n  \"name\": \"prqlc\",\n  \"bugs\": {\n    \"url\": \"https://github.com/PRQL/prql/issues\"\n  },\n  \"homepage\": \"https://prql-lang.org/\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/PRQL/prql.git\",\n    \"directory\": \"prqlc/bindings/js\"\n  },\n  \"keywords\": [\n    \"prql\"\n  ],\n  \"scripts\": {\n    \"build\": \"npm run build:node && npm run build:web && npm run build:bundler\",\n    \"build:bundler\": \"npx cross-env wasm-pack build --target bundler --out-dir dist/bundler --${PROFILE} && rm dist/bundler/.gitignore\",\n    \"build:node\": \"npx cross-env wasm-pack build --target nodejs --out-dir dist/node --${PROFILE} && rm dist/node/.gitignore\",\n    \"build:web\": \"npx cross-env wasm-pack build --target web --out-dir dist/web --${PROFILE} && rm dist/web/.gitignore\",\n    \"prepare\": \"npm run build\",\n    \"test\": \"mocha tests\"\n  },\n  \"types\": \"dist/node/prqlc_js.d.ts\",\n  \"version\": \"0.13.12\"\n}\n"
  },
  {
    "path": "prqlc/bindings/js/src/lib.rs",
    "content": "#![cfg(target_family = \"wasm\")]\n\nuse std::{default::Default, str::FromStr};\n\nuse prqlc::Target;\nuse wasm_bindgen::prelude::*;\n\n#[wasm_bindgen]\npub fn compile(prql_query: &str, options: Option<CompileOptions>) -> Option<String> {\n    return_or_throw(\n        prqlc::compile(prql_query, &options.map(|x| x.into()).unwrap_or_default())\n            .map_err(|e| e.composed(&prql_query.into())),\n    )\n}\n\n#[wasm_bindgen]\npub fn prql_to_pl(prql_query: &str) -> Option<String> {\n    return_or_throw(\n        Ok(prql_query)\n            .and_then(prqlc::prql_to_pl)\n            .and_then(|x| prqlc::json::from_pl(&x)),\n    )\n}\n\n#[wasm_bindgen]\npub fn pl_to_prql(pl_json: &str) -> Option<String> {\n    return_or_throw(\n        Ok(pl_json)\n            .and_then(prqlc::json::to_pl)\n            .and_then(|x| prqlc::pl_to_prql(&x)),\n    )\n}\n\n#[wasm_bindgen]\npub fn pl_to_rq(pl_json: &str) -> Option<String> {\n    return_or_throw(\n        Ok(pl_json)\n            .and_then(prqlc::json::to_pl)\n            .and_then(prqlc::pl_to_rq)\n            .and_then(|x| prqlc::json::from_rq(&x)),\n    )\n}\n\n#[wasm_bindgen]\npub fn rq_to_sql(rq_json: &str) -> Option<String> {\n    return_or_throw(\n        Ok(rq_json)\n            .and_then(prqlc::json::to_rq)\n            .and_then(|x| prqlc::rq_to_sql(x, &prqlc::Options::default())),\n    )\n}\n\n/// Compilation options for SQL backend of the compiler.\n#[wasm_bindgen]\n#[derive(Clone)]\npub struct CompileOptions {\n    /// Pass generated SQL string through a formatter that splits it\n    /// into multiple lines and prettifies indentation and spacing.\n    ///\n    /// Defaults to true.\n    pub format: bool,\n\n    #[wasm_bindgen(skip)]\n    pub target: String,\n\n    /// Emits the compiler signature as a comment after generated SQL\n    ///\n    /// Defaults to true.\n    pub signature_comment: bool,\n}\n\n#[wasm_bindgen]\npub fn get_targets() -> Vec<JsValue> {\n    prqlc::Target::names()\n        .iter()\n        .map(|t| JsValue::from_str(t))\n        .collect()\n}\n\nimpl Default for CompileOptions {\n    fn default() -> Self {\n        Self {\n            format: true,\n            target: String::new(),\n            signature_comment: true,\n        }\n    }\n}\n\n#[wasm_bindgen]\nimpl CompileOptions {\n    #[wasm_bindgen(constructor)]\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Target to compile to (e.g. sql.postgres)\n    ///\n    /// Defaults to `sql.any`, which uses `target` argument from the query header to determine\n    /// the SQL dialect.\n    #[wasm_bindgen(getter)]\n    pub fn target(&self) -> String {\n        self.target.clone()\n    }\n\n    #[wasm_bindgen(setter)]\n    pub fn set_target(&mut self, target: String) {\n        self.target = target;\n    }\n}\n\nimpl From<CompileOptions> for prqlc::Options {\n    fn from(o: CompileOptions) -> Self {\n        let target = Target::from_str(&o.target).unwrap_or_default();\n\n        prqlc::Options {\n            format: o.format,\n            target,\n            signature_comment: o.signature_comment,\n            display: prqlc::DisplayOptions::Plain,\n            ..Default::default()\n        }\n    }\n}\n\nfn return_or_throw(result: Result<String, prqlc::ErrorMessages>) -> Option<String> {\n    // When the `console_error_panic_hook` feature is enabled, we can call the\n    // `set_panic_hook` function at least once during initialization, and then\n    // we will get better error messages if our code ever panics. See\n    // `Cargo.toml` for notes.\n    #[cfg(feature = \"console_error_panic_hook\")]\n    console_error_panic_hook::set_once();\n\n    match result {\n        Ok(sql) => Some(sql),\n        Err(e) => wasm_bindgen::throw_str(&e.to_json()),\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/js/tests/test_all.mjs",
    "content": "import assert from \"assert\";\nimport { expect } from \"chai\";\nimport prqlc from \"../dist/node/prqlc_js.js\";\n\nconst employee_prql = `from employees\njoin salaries (==emp_no)\ngroup {employees.emp_no, employees.gender} (\n  aggregate {\n    emp_salary = average salaries.salary\n  }\n)\njoin de=dept_emp (==emp_no)\njoin dm=dept_manager (\n  (dm.dept_no == de.dept_no) && s\"(de.from_date, de.to_date) OVERLAPS (dm.from_date, dm.to_date)\"\n)\ngroup {dm.emp_no, gender} (\n  aggregate {\n    salary_avg = average emp_salary,\n    salary_sd = stddev emp_salary\n  }\n)\nderive mng_no = emp_no\njoin managers=employees (==emp_no)\nderive mng_name = s\"managers.first_name || ' ' || managers.last_name\"\nselect {mng_name, managers.gender, salary_avg, salary_sd}`;\n\ndescribe(\"prqlc-js\", () => {\n  describe(\"compile\", () => {\n    it(\"should return valid sql from valid prql\", () => {\n      const sql = prqlc.compile(employee_prql);\n      assert(\n        sql.trim().toLowerCase().startsWith(\"with\") ||\n          sql.trim().toLowerCase().startsWith(\"select\"),\n      );\n    });\n\n    it(\"should throw an error on invalid prql\", () => {\n      expect(() =>\n        prqlc.compile(\"Mississippi has four Ss and four Is.\"),\n      ).to.throw(\"Error\");\n    });\n\n    it(\"should compile to dialect\", () => {\n      const opts = new prqlc.CompileOptions();\n      opts.target = \"sql.mssql\";\n      opts.format = false;\n      opts.signature_comment = false;\n\n      const res = prqlc.compile(\"from a | take 10\", opts);\n      assert.equal(\n        res,\n        \"SELECT * FROM a ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY\",\n      );\n    });\n\n    it(\"CompileOptions should be preferred and should ignore target in header\", () => {\n      const opts = new prqlc.CompileOptions();\n      opts.target = \"sql.mssql\";\n      opts.format = false;\n      opts.signature_comment = true;\n\n      const res = prqlc.compile(\n        \"prql target:sql.sqlite\\nfrom a | take 10\",\n        opts,\n      );\n      assert(\n        res.includes(\n          \"SELECT * FROM a ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY\",\n        ),\n      );\n      assert(res.includes(\"target:sql.mssql\"));\n    });\n  });\n\n  describe(\"prql_to_pl\", () => {\n    it(\"should return valid json from valid prql\", () => {\n      JSON.parse(prqlc.prql_to_pl(employee_prql));\n    });\n\n    it(\"should throw an error on invalid prql\", () => {\n      expect(() => prqlc.prql_to_pl(\"Answer: T-H-A-T!\")).to.throw(\"Error\");\n    });\n  });\n\n  describe(\"CompileOptions\", () => {\n    it(\"should be able to create from constructor\", () => {\n      const opts = new prqlc.CompileOptions();\n\n      opts.target = \"sql.sqlite\";\n      assert.equal(opts.target, \"sql.sqlite\");\n    });\n\n    it(\"should fallback to the target in header\", () => {\n      const opts = new prqlc.CompileOptions();\n\n      opts.target = \"sql.any\";\n      const res = prqlc.compile(\"prql target:sql.mssql\\nfrom a | take 1\", opts);\n      assert(res.includes(\"1 ROWS ONLY\"));\n    });\n  });\n\n  describe(\"get_targets\", () => {\n    it(\"return a list of targets\", () => {\n      const targets = new prqlc.get_targets();\n      assert(targets.length > 0);\n      assert(targets.includes(\"sql.sqlite\"));\n    });\n  });\n\n  describe(\"compile error\", () => {\n    it(\"should contain json\", () => {\n      try {\n        prqlc.compile(\"from x | select a | select b\");\n      } catch (error) {\n        const errorMessages = JSON.parse(error.message).inner;\n\n        assert(errorMessages.length > 0);\n        assert(errorMessages[0].display.includes(\"\\n\"));\n        assert(!errorMessages[0].reason.includes(\"\\n\"));\n      }\n    });\n\n    it(\"should contain error code\", () => {\n      try {\n        prqlc.compile(\"let a = (from x)\");\n      } catch (error) {\n        const errorMessages = JSON.parse(error.message).inner;\n\n        assert(errorMessages[0].code == \"E0001\");\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "prqlc/bindings/php/.editorconfig",
    "content": "root = true\n\n[*.php]\nindent_size = 4\nindent_style = space\n"
  },
  {
    "path": "prqlc/bindings/php/.gitignore",
    "content": "vendor/\nlib/\n"
  },
  {
    "path": "prqlc/bindings/php/README.md",
    "content": "# prql-php\n\n`prql-php` offers PHP bindings to `prqlc` crate through FFI.\n\nIt provides the `Compiler` class which contains `compile`, `prqlToPL`, `plToRQ`\nand `rqToSQL` functions.\n\nIt's still at an early stage, and isn't published to Composer. Contributions are\nwelcome.\n\n## Installation\n\nThe [PHP FFI extension](https://www.php.net/manual/en/book.ffi.php) needs to be\nenabled. Set `ffi.enable` in your php.ini configuration file to `\"true\"`.\n\n## Usage\n\n```php\n<?php\n\nuse Prql\\Compiler\\Compiler;\n\n$prql = new Compiler();\n$result = $prql->compile(\"from employees\");\n\necho $result->output;\n```\n\n## Development\n\n### Environment\n\nA way to establish a dev environment with PHP, the ext-ffi extension and\nComposer is to use a [nix flake](https://github.com/loophp/nix-shell). After\ninstalling nix, enable experimental flakes feature:\n\n```sh\nmkdir -p ~/.config/nix\necho \"experimental-features = nix-command flakes\" >> ~/.config/nix/nix.conf\n```\n\nNow you can spawn a shell from `prql-php/`:\n\n```sh\nnix shell github:loophp/nix-shell#env-php81 --impure\n```\n\nThis will pull-in ext-ffi extension, because it's declared in `composer.json`.\n\n### Building\n\nThere is a `task build-php` script that:\n\n- runs cargo to build `libprqlc_c`,\n- copies `libprqlc_c.*` into `lib`,\n- copies `prqlc.h` into `lib`.\n\n### Tests\n\n```sh\ntask build-php\ntask test-php\n```\n\n### Code style\n\n```sh\n./vendor/bin/phpcs --standard=PSR12 src tests\n```\n"
  },
  {
    "path": "prqlc/bindings/php/composer.json",
    "content": "{\n  \"name\": \"prql/compiler\",\n  \"description\": \"PRQL compiler bindings.\",\n  \"keywords\": [\n    \"prql\",\n    \"sql\"\n  ],\n  \"homepage\": \"https://prql-lang.org/\",\n  \"type\": \"library\",\n  \"license\": \"Apache-2.0\",\n  \"autoload\": {\n    \"psr-4\": {\n      \"Prql\\\\Compiler\\\\\": \"src/\"\n    }\n  },\n  \"autoload-dev\": {\n    \"psr-4\": {\n      \"Prql\\\\Tests\\\\\": \"tests/\"\n    }\n  },\n  \"authors\": [\n    {\n      \"name\": \"Jonathan\"\n    }\n  ],\n  \"support\": {\n    \"issues\": \"https://github.com/PRQL/prql/issues\",\n    \"source\": \"https://github.com/PRQL/prql\",\n    \"docs\": \"https://prql-lang.org/book/\"\n  },\n  \"require\": {\n    \"php\": \"^8.1\",\n    \"ext-ffi\": \"*\"\n  },\n  \"require-dev\": {\n    \"phpunit/phpunit\": \"^10\",\n    \"squizlabs/php_codesniffer\": \"^3.7\"\n  }\n}\n"
  },
  {
    "path": "prqlc/bindings/php/phpstan.neon",
    "content": "parameters:\n    level: 9\n    paths:\n        - src\n        - tests\n    ignoreErrors:\n        # Since PHPStan doesn't know about the existence of the functions in libprql,\n        # it complains since they get dynamically invoked on the FFI object instance\n        # without being statically defined, so they're unknown.\n        - '#Call to an undefined method FFI::compile\\(\\)\\.#'\n        - '#Call to an undefined method FFI::prql_to_pl\\(\\)\\.#'\n        - '#Call to an undefined method FFI::pl_to_rq\\(\\)\\.#'\n        - '#Call to an undefined method FFI::rq_to_sql\\(\\)\\.#'\n        - '#Cannot access property \\$format on FFI\\\\CData\\|null\\.#'\n        - '#Cannot access property \\$signature_comment on FFI\\\\CData\\|null\\.#'\n        - '#Cannot access property \\$target on FFI\\\\CData\\|null\\.#'\n"
  },
  {
    "path": "prqlc/bindings/php/src/Compiler.php",
    "content": "<?php\n\n/**\n * PRQL compiler bindings.\n *\n * This library requires the PHP FFI extension.\n * It also requires the libprqlc_c library.\n *\n * PHP version 8.0\n *\n * @api\n *\n * @author    PRQL\n * @copyright 2023 PRQL\n * @license   https://spdx.org/licenses/Apache-2.0.html Apache License 2.0\n *\n * @link https://prql-lang.org/\n */\n\ndeclare(strict_types=1);\n\nnamespace Prql\\Compiler;\n\n/**\n * PRQL Compiler.\n *\n * @author  PRQL\n * @license https://spdx.org/licenses/Apache-2.0.html Apache License 2.0\n *\n * @link https://prql-lang.org/\n */\nfinal class Compiler\n{\n    private static \\FFI $ffi;\n\n    /**\n     * Initializes a new instance of the Compiler.\n     *\n     * @param string|null $lib_path path to the libprql library\n     */\n    public function __construct(?string $lib_path = null)\n    {\n        if (isset(self::$ffi)) {\n            return;\n        }\n\n        if ($lib_path === null) {\n            $lib_path = __DIR__ . '/../lib';\n        }\n\n        $header = $lib_path . '/prqlc.h';\n\n        if (PHP_OS_FAMILY === 'Windows') {\n            $library = $lib_path . \"\\libprqlc_c.dll\";\n        } elseif (PHP_OS_FAMILY === 'Darwin') {\n            $library = $lib_path . '/libprqlc_c.dylib';\n        } else {\n            $library = $lib_path . '/libprqlc_c.so';\n        }\n\n        $header_source = file_get_contents($header, false, null, 0, 1024 * 1024);\n\n        if ($header_source === false) {\n            throw new \\InvalidArgumentException('Cannot load header file.');\n        }\n\n        self::$ffi = \\FFI::cdef($header_source, $library);\n    }\n\n    /**\n     * Compile a PRQL string into a SQL string.\n     *\n     * @param string       $prql_query a PRQL query\n     * @param Options|null $options    compile options\n     *\n     * @return Result compilation result containing SQL query\n     *\n     * @throws \\InvalidArgumentException on NULL input\n     */\n    public function compile(string $prql_query, ?Options $options = null): Result\n    {\n        if (!$prql_query) {\n            throw new \\InvalidArgumentException('No query given.');\n        }\n\n        $ffi_options = $this->optionsInit($options);\n\n        $res = self::$ffi->compile($prql_query, \\FFI::addr($ffi_options));\n\n        $this->optionsDestroy($ffi_options);\n\n        return $this->convertResult($res);\n    }\n\n    /**\n     * Compile a PRQL string into PL.\n     *\n     * @param string $prql_query PRQL query\n     *\n     * @return Result compilation result containing PL serialized as JSON\n     *\n     * @throws \\InvalidArgumentException on NULL input\n     *\n     * @api\n     */\n    public function prqlToPL(string $prql_query): Result\n    {\n        if (!$prql_query) {\n            throw new \\InvalidArgumentException('No query given.');\n        }\n\n        $res = self::$ffi->prql_to_pl($prql_query);\n\n        return $this->convertResult($res);\n    }\n\n    /**\n     * Converts PL to RQ.\n     *\n     * @param string $pl_json PL serialized as JSON\n     *\n     * @return Result compilation result containing RQ serialized as JSON\n     *\n     * @throws \\InvalidArgumentException on NULL input\n     *\n     * @api\n     */\n    public function plToRQ(string $pl_json): Result\n    {\n        if (!$pl_json) {\n            throw new \\InvalidArgumentException('No query given.');\n        }\n\n        $res = self::$ffi->pl_to_rq($pl_json);\n\n        return $this->convertResult($res);\n    }\n\n    /**\n     * Converts RQ to SQL.\n     *\n     * @param string       $rq_json RQ serialized as JSON\n     * @param Options|null $options compile options\n     *\n     * @return Result compilation result containing SQL query\n     *\n     * @throws \\InvalidArgumentException on NULL input\n     *\n     * @api\n     */\n    public function rqToSQL(string $rq_json, ?Options $options = null): Result\n    {\n        if (!$rq_json) {\n            throw new \\InvalidArgumentException('No query given.');\n        }\n\n        $ffi_options = $this->optionsInit($options);\n\n        $res = self::$ffi->rq_to_sql($rq_json, \\FFI::addr($ffi_options));\n\n        $this->optionsDestroy($ffi_options);\n\n        return $this->convertResult($res);\n    }\n\n    private function optionsInit(?Options $options = null)\n    {\n        if ($options === null) {\n            $options = new Options();\n        }\n\n        $ffi_options = self::$ffi->new('struct Options');\n        $ffi_options->format = $options->format;\n        $ffi_options->signature_comment = $options->signature_comment;\n\n        if (isset($options->target)) {\n            $len = strlen($options->target) + 1;\n            $ffi_options->target = \\FFI::new(\"char[$len]\", false);\n            \\FFI::memcpy($ffi_options->target, $options->target, $len - 1);\n        }\n\n        return $ffi_options;\n    }\n\n    private function optionsDestroy($ffi_options)\n    {\n        if (!\\FFI::isNull($ffi_options->target)) {\n            \\FFI::free($ffi_options->target);\n        }\n\n        unset($ffi_options);\n    }\n\n    private function convertResult($ffi_res): Result\n    {\n        $res = new Result();\n\n        // convert string\n        $res->output = $ffi_res->output;\n\n\n        $res->messages = [];\n        for ($i = 0; $i < $ffi_res->messages_len; ++$i) {\n            $res->messages[$i] = $this->convertMessage($ffi_res->messages[$i]);\n        }\n\n        // free the ffi_result\n        self::$ffi->result_destroy($ffi_res);\n\n        return $res;\n    }\n\n    private function convertMessage($ffi_msg): Message\n    {\n        $msg = new Message();\n\n        // I'm using numbers here, I cannot find a way to refer to MessageKind.Error\n        if ($ffi_msg->kind == 0) {\n            $msg->kind = MessageKind::Error;\n        } elseif ($ffi_msg->kind == 1) {\n            $msg->kind = MessageKind::Warning;\n        } elseif ($ffi_msg->kind == 2) {\n            $msg->kind = MessageKind::Lint;\n        }\n\n        $msg->code = $this->convertNullableString($ffi_msg->code);\n        $msg->reason = $ffi_msg->reason;\n        $msg->span = $this->convertSpan($ffi_msg->span);\n        $msg->hint = $this->convertNullableString($ffi_msg->hint);\n\n        $msg->display = $this->convertNullableString($ffi_msg->display);\n        $msg->location = $this->convertLocation($ffi_msg->location);\n\n        return $msg;\n    }\n\n    private function convertSpan($ffi_ptr): ?Span\n    {\n        if (is_null($ffi_ptr) || \\FFI::isNull($ffi_ptr)) {\n            return null;\n        }\n\n        $span = new Span();\n        $span->start = $ffi_ptr[0]->start;\n        $span->end = $ffi_ptr[0]->end;\n\n        return $span;\n    }\n\n    private function convertLocation($ffi_ptr): ?SourceLocation\n    {\n        if (is_null($ffi_ptr) || \\FFI::isNull($ffi_ptr)) {\n            return null;\n        }\n\n        $location = new SourceLocation();\n        $location->start_line = $ffi_ptr[0]->start_line;\n        $location->start_col = $ffi_ptr[0]->start_col;\n        $location->end_line = $ffi_ptr[0]->end_line;\n        $location->end_col = $ffi_ptr[0]->end_col;\n\n        return $location;\n    }\n\n    private function convertNullableString($ffi_ptr): ?string\n    {\n        if (is_null($ffi_ptr) || \\FFI::isNull($ffi_ptr)) {\n            return null;\n        }\n        return $ffi_ptr[0];\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/php/src/Message.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prql\\Compiler;\n\n/**\n * Compile result message.\n */\nfinal class Message\n{\n    /**\n     * Message kind. Currently only Error is implemented.\n     */\n    public MessageKind $kind;\n    /**\n     * Machine-readable identifier of the error.\n     */\n    public ?string $code = null;\n    /**\n     * Plain text of the error.\n     */\n    public string $reason;\n    /**\n     * A list of suggestions of how to fix the error.\n     */\n    public ?string $hint = null;\n    /**\n     * Character offset of error origin within a source file.\n     */\n    public ?Span $span = null;\n    /**\n     * Annotated code, containing cause and hints.\n     */\n    public ?string $display = null;\n    /**\n     * Line and column number of error origin within a source file.\n     */\n    public ?SourceLocation $location = null;\n}\n"
  },
  {
    "path": "prqlc/bindings/php/src/MessageKind.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prql\\Compiler;\n\n/**\n * Compile message kind. Currently only Error is implemented.\n */\nenum MessageKind\n{\n    /**\n     * Error message.\n     */\n    case Error;\n    /**\n     * Warning message.\n     */\n    case Warning;\n    /**\n     * Lint message.\n     */\n    case Lint;\n}\n"
  },
  {
    "path": "prqlc/bindings/php/src/Options.php",
    "content": "<?php\n\n/**\n * PRQL compiler bindings.\n *\n * This library requires the PHP FFI extension.\n * It also requires the libprqlc_c library.\n *\n * PHP version 8.0\n *\n * @api\n *\n * @author    PRQL\n * @copyright 2023 PRQL\n * @license   https://spdx.org/licenses/Apache-2.0.html Apache License 2.0\n *\n * @see https://prql-lang.org/\n */\n\ndeclare(strict_types=1);\n\nnamespace Prql\\Compiler;\n\n/**\n * Compilation options for SQL backend of the compiler.\n *\n * @author  PRQL\n * @license https://spdx.org/licenses/Apache-2.0.html Apache License 2.0\n *\n * @see https://prql-lang.org/\n */\nfinal class Options\n{\n    /**\n     * Pass generated SQL string through a formatter that splits it into\n     * multiple lines and prettifies indentation and spacing.\n     */\n    public bool $format = true;\n\n    /**\n     * Target and dialect to compile to.\n     */\n    public ?string $target = null;\n\n    /**\n     * Emits the compiler signature as a comment after generated SQL.\n     */\n    public bool $signature_comment = true;\n}\n"
  },
  {
    "path": "prqlc/bindings/php/src/Result.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prql\\Compiler;\n\n/**\n * Result of compilation.\n */\nfinal class Result\n{\n    /**\n     * @var string\n     */\n    public string $output;\n\n    /**\n     * @var array<Message>\n     */\n    public array $messages;\n}\n"
  },
  {
    "path": "prqlc/bindings/php/src/SourceLocation.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prql\\Compiler;\n\n/**\n * Location within a source file.\n */\nfinal class SourceLocation\n{\n    /**\n     * Start line.\n     */\n    public int $start_line;\n\n    /**\n     * Start column.\n     */\n    public int $start_col;\n\n    /**\n     * End line.\n     */\n    public int $end_line;\n\n    /**\n     * End column.\n     */\n    public int $end_col;\n}\n"
  },
  {
    "path": "prqlc/bindings/php/src/Span.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prql\\Compiler;\n\n/**\n * Identifier of a location in source.\n * Contains offsets in terms of chars.\n */\nfinal class Span\n{\n    /**\n     * Start offset.\n     */\n    public int $start;\n\n    /**\n     * End offset.\n     */\n    public int $end;\n}\n"
  },
  {
    "path": "prqlc/bindings/php/tests/CompilerTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prql\\Compiler\\Test;\n\nuse Prql\\Compiler\\Compiler;\nuse Prql\\Compiler\\Options;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class CompilerTest extends TestCase\n{\n    public function testFfiExtensionIsLoaded(): void\n    {\n        $this->assertTrue(extension_loaded(\"ffi\"));\n    }\n\n    public function testPrqlLibraryFileExists(): void\n    {\n        $fileExists = file_exists(\"lib/libprqlc_c.so\")\n                  || file_exists(\"lib/libprqlc_c.dylib\")\n                  || file_exists(\"lib/libprqlc_c.dll\");\n\n        $this->assertTrue($fileExists);\n    }\n\n    public function testPrqlHeaderFileExists(): void\n    {\n        $this->assertFileExists(\"lib/prqlc.h\");\n    }\n\n    public function testInvalidQuery(): void\n    {\n        $prql = new Compiler();\n        $res = $prql->compile(\"invalid\");\n\n        $this->assertCount(1, $res->messages);\n    }\n\n    public function testCompileWorks(): void\n    {\n        $options = new Options();\n        $options->format = false;\n        $options->signature_comment = false;\n        $options->target = \"sql.mssql\";\n        $prql = new Compiler();\n\n        $actual = $prql->compile(\"from employees | take 10\", $options);\n        $this->assertCount(0, $actual->messages);\n\n        $this->assertEquals(\n            \"SELECT * FROM employees ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY\",\n            $actual->output\n        );\n    }\n\n    public function testOtherFunctions(): void\n    {\n        $prql = new Compiler();\n\n        $query = \"\n            let a = (from employees | take 10)\n\n            from a | select {first_name}\n        \";\n\n        $pl = $prql->prqlToPL($query);\n        $this->assertCount(0, $pl->messages);\n\n        $rq = $prql->plToRQ($pl->output);\n        $this->assertCount(0, $rq->messages);\n\n        $via_json = $prql->rqToSQL($rq->output);\n        $this->assertCount(0, $via_json->messages);\n\n        $direct = $prql->compile($query);\n        $this->assertCount(0, $direct->messages);\n\n        $this->assertEquals($via_json, $direct);\n    }\n}\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/Cargo.toml",
    "content": "[package]\nname = \"prqlc-c\"\npublish = false\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nversion.workspace = true\n\n# This means we can build with `--features=default`, which can make builds more generic\n[features]\ndefault = []\n\n[lib]\n# We produce both of these at the moment, but could consider refining this. ref\n# https://github.com/rust-lang/cargo/issues/8607 &\n# https://github.com/rust-lang/rust/issues/59302\nbench = false\ncrate-type = [\"staticlib\", \"cdylib\"]\ndoc = false\ndoctest = false\ntest = false\n\n[dependencies]\nlibc = \"0.2.183\"\nprqlc = {path = \"../../prqlc\", default-features = false}\nserde_json = {workspace = true}\n\n[package.metadata.release]\ntag-name = \"{{version}}\"\ntag-prefix = \"\"\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/README.md",
    "content": "# PRQL C library\n\n## Description\n\nThis module compiles PRQL as a library (both `.a` and `.so` are generated). This\nallows embedding in languages that support FFI — for example, Golang.\n\n## Linking\n\nSee [examples/minimal-c/Makefile](examples/minimal-c/Makefile).\n\nCopy the `.a` and `.so` files in a convenient place and add the following\ncompile flags to Go (cgo):\n\n`CGO_LDFLAGS=\"-L/path/to/libprqlc_c.a -lprqlc -pthread -ldl\" go build`\n\n## Examples\n\nFor a minimal example, see\n[examples/minimal-c/main.c](examples/minimal-c/main.c).\n\nBelow is an example from an actual application that is using PRQL in Go.\n\n```go\npackage prql\n\n/*\n\n\n#include <stdlib.h>\n\nint to_sql(char *prql_query, char *sql_query);\nint to_json(char *prql_query, char *json_query);\n\n*/\nimport \"C\"\n\nimport (\n    \"errors\"\n    \"strings\"\n    \"unsafe\"\n)\n\n// ToSQL converts a PRQL query to SQL\nfunc ToSQL(prql string) (string, error) {\n    // buffer length should not be less than 1K because we may get an error\n    // from the PRQL compiler with a very short query\n    cStringBufferLength := 1024\n\n    // allocate a buffer 3 times the length of the PRQL query to store the\n    // generated SQL query\n    if len(prql)*3 > cStringBufferLength {\n        cStringBufferLength = len(prql) * 3\n    }\n\n    // preallocate the buffer\n    cstr := C.CString(strings.Repeat(\" \", cStringBufferLength))\n    defer C.free(unsafe.Pointer(cstr))\n\n    // convert the PRQL query to SQL\n    res := C.to_sql(C.CString(prql), cstr)\n    if res == 0 {\n        return C.GoString(cstr), nil\n    }\n\n    return \"\", errors.New(C.GoString(cstr))\n}\n\n// ToJSON converts a PRQL query to JSON\nfunc ToJSON(prql string) (string, error) {\n    // buffer length should not be less than 1K because we may get an error\n    cStringBufferLength := 1024\n    if len(prql)*3 > cStringBufferLength {\n        cStringBufferLength = len(prql) * 10\n    }\n\n    // preallocate the buffer\n    cstr := C.CString(strings.Repeat(\" \", cStringBufferLength))\n    defer C.free(unsafe.Pointer(cstr))\n\n    // convert the PRQL query to SQL\n    res := C.to_json(C.CString(prql), cstr)\n    if res == 0 {\n        return C.GoString(cstr), nil\n    }\n\n    return \"\", errors.New(C.GoString(cstr))\n}\n```\n\n## Development\n\n### Headers\n\nThe C & C++ header files `prqlc.h` & `prqlc.hpp` were generated using\n[cbindgen](https://github.com/eqrion/cbindgen). To generate a new one run:\n\n```sh\ntask build-prqlc-c-header\n```\n\n...or copy & paste the commands from the Taskfile.\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/cbindgen.toml",
    "content": "language = \"C\"\n\nheader = '''/*\n * PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement\n *\n * License: Apache-2.0\n * Website: https://prql-lang.org/\n */'''\n\nautogen_warning = \"/* This file is autogenerated. Do not modify this file manually. */\"\n\nnamespace = \"prqlc\"\n\nafter_includes = '#define FFI_SCOPE \"PRQL\"'\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-c/Makefile",
    "content": "PRQL_PROJECT=../../../../..\n\nrun: build\n\t./main.out\n\nbuild-prql:\n\tcargo build --package prqlc-c --release\n\nUNAME_S := $(shell uname -s)\n\nLD_FLAGS = -L${PRQL_PROJECT}/target/release \\\n\t${PRQL_PROJECT}/target/release/libprqlc_c.a \\\n\t-pthread -ldl -lm\n\nifeq ($(UNAME_S),Darwin)\n\tLD_FLAGS := $(LD_FLAGS) -framework CoreFoundation\nendif\n\n# TODO: would be helpful to allow running with a debug build too.\nbuild: main.c build-prql\n\tgcc main.c -o main.out \\\n\t\t-I${PRQL_PROJECT}/prqlc/bindings/prqlc-c \\\n\t\t$(LD_FLAGS)\n\nvalgrind: build\n\tvalgrind ./main.out\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-c/README.md",
    "content": "# Basic C example\n\nA minimal example for using prql-lib with `gcc` and `make`.\n\n## How to run\n\n      make run\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-c/main.c",
    "content": "#include <stdio.h>\n\n#include <prqlc.h>\n\nvoid print_result(CompileResult res) {\n  printf(\"---- [ Compiled with %ld errors ]----\\n\", res.messages_len);\n  for (int i = 0; i < res.messages_len; i++) {\n    Message const *e = &res.messages[i];\n    if (e->display != NULL) {\n      printf(\"%s\", *e->display);\n    } else if (e->code != NULL) {\n      printf(\"[%s] Error: %s\\n\", *e->code, e->reason);\n    } else {\n      printf(\"Error: %s\", e->reason);\n    }\n  }\n  if (*res.output == '\\0') {\n    printf(\"Output: <empty>\\n\\n\");\n  } else {\n    printf(\"Output:\\n%s\\n\\n\", res.output);\n  }\n}\n\nint main() {\n  char *prql_query;\n  prql_query = \"from albums | select {album_id, title} | take 3\";\n  CompileResult res;\n  CompileResult res2;\n\n  // default compile option\n  res = compile(prql_query, NULL);\n  print_result(res);\n  if (res.messages_len != 0)\n    return 1;\n  result_destroy(res);\n\n  // custom compile options\n  Options opts;\n  opts.format = false;\n  opts.signature_comment = false;\n  opts.target = \"sql.mssql\";\n  res = compile(prql_query, &opts);\n  print_result(res);\n  if (res.messages_len != 0)\n    return 1;\n  result_destroy(res);\n\n  // error handling\n  res = compile(\"from album | select {album_id} | select {title}\", NULL);\n  print_result(res);\n  if (res.messages_len == 0)\n    return 1;\n  result_destroy(res);\n\n  // error handling\n  res = compile(\"let a = (from album)\", NULL);\n  print_result(res);\n  if (res.messages_len == 0)\n    return 1;\n  result_destroy(res);\n\n  // intermediate results\n  res = prql_to_pl(prql_query);\n  print_result(res);\n\n  res2 = pl_to_rq(res.output);\n  result_destroy(res);\n\n  print_result(res2);\n  result_destroy(res2);\n\n  return 0;\n}\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-cpp/Makefile",
    "content": "PRQL_PROJECT=../../../../..\n\nrun: build\n\t./main.out\n\nbuild-prql:\n\tcargo build --package prqlc-c --release\n\nUNAME_S := $(shell uname -s)\n\nLD_FLAGS = -L${PRQL_PROJECT}/target/release \\\n\t${PRQL_PROJECT}/target/release/libprqlc_c.a\n\nifeq ($(UNAME_S),Darwin)\n\tLD_FLAGS := $(LD_FLAGS) -framework CoreFoundation\nendif\n\n# TODO: would be helpful to allow running with a debug build too.\nbuild: main.cpp build-prql\n\tg++ main.cpp -o main.out \\\n\t\t-I${PRQL_PROJECT}/prqlc/bindings/prqlc-c \\\n\t\t$(LD_FLAGS)\n\nvalgrind: build\n\tvalgrind ./main.out\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-cpp/README.md",
    "content": "# Basic C++ example\n\nA minimal example for using prqlc-c with `gcc` and `make`.\n\n## How to run\n\n    make run\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-cpp/main.cpp",
    "content": "#include <cstring>\n#include <iostream>\n\n#include \"prqlc.hpp\"\n\nusing namespace prqlc;\n\nvoid print_result(CompileResult res) {\n  if (strcmp(res.output, \"\") == 0) {\n    std::cout << \"Output: <empty>\\n\\n\";\n  } else {\n    std::cout << \"Output:\\n\\n\" << res.output;\n  }\n}\n\nint main() {\n  const auto prql_query = \"from albums | select {album_id, title} | take 3\";\n\n  CompileResult res = compile(prql_query, nullptr);\n  print_result(res);\n  result_destroy(res);\n\n  return 0;\n}\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-zig/.gitignore",
    "content": "zig-cache/\nzig-out/\nc/\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-zig/README.md",
    "content": "# Basic Zig example\n\nA minimal example for using prql-lib with Zig.\n\nRun with `task zig` from the root of the repo.\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-zig/Taskfile.yaml",
    "content": "# yaml-language-server: $schema=https://json.schemastore.org/taskfile.json\n\nversion: 3\n\nvars:\n  project_root: \"../../../../..\"\n\ntasks:\n  run:\n    deps:\n      - task: build\n    cmds:\n      - ./zig-out/bin/minimal-zig\n\n  build-prql:\n    desc: \"Build prqlc-c\"\n    cmds:\n      - cargo build --package prqlc-c --release\n      - mkdir -p c/\n      - cp {{.project_root}}/prqlc/bindings/prqlc-c/prqlc.h c/\n      - cp {{.project_root}}/target/release/libprqlc_c.* c/\n\n  build:\n    desc: \"Build the project\"\n    cmds:\n      - zig build\n\n  test:\n    desc: \"Run tests\"\n    deps:\n      - task: build\n    cmds:\n      - zig build test\n\n  default:\n    desc: \"Build, run, test\"\n    cmds:\n      - task: build-prql\n      - task: build\n      - task: run\n      - task: test\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-zig/build.zig",
    "content": "const std = @import(\"std\");\n\n// Although this function looks imperative, note that its job is to\n// declaratively construct a build graph that will be executed by an external\n// runner.\npub fn build(b: *std.Build) void {\n    // Standard target options allows the person running `zig build` to choose\n    // what target to build for. Here we do not override the defaults, which\n    // means any target is allowed, and the default is native. Other options\n    // for restricting supported target set are available.\n    const target = b.standardTargetOptions(.{});\n\n    // Standard optimization options allow the person running `zig build` to select\n    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not\n    // set a preferred release mode, allowing the user to decide how to optimize.\n    const optimize = b.standardOptimizeOption(.{});\n\n    const exe = b.addExecutable(.{\n        .name = \"minimal-zig\",\n        // In this case the main source file is merely a path, however, in more\n        // complicated build scripts, this could be a generated file.\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/main.zig\"),\n            .target = target,\n            .optimize = optimize,\n        }),\n    });\n    exe.addIncludePath(b.path(\"src\"));\n    exe.addLibraryPath(b.path(\"c\"));\n    exe.installHeader(b.path(\"c/prqlc.h\"), \"prqlc.h\");\n    exe.linkSystemLibrary(\"prqlc_c\");\n    exe.linkLibC();\n\n    // This declares intent for the executable to be installed into the\n    // standard location when the user invokes the \"install\" step (the default\n    // step when running `zig build`).\n    b.installArtifact(exe);\n\n    // This *creates* a Run step in the build graph, to be executed when another\n    // step is evaluated that depends on it. The next line below will establish\n    // such a dependency.\n    const run_cmd = b.addRunArtifact(exe);\n\n    // By making the run step depend on the install step, it will be run from the\n    // installation directory rather than directly from within the cache directory.\n    // This is not necessary, however, if the application depends on other installed\n    // files, this ensures they will be present and in the expected location.\n    run_cmd.step.dependOn(b.getInstallStep());\n\n    // This allows the user to pass arguments to the application in the build\n    // command itself, like this: `zig build run -- arg1 arg2 etc`\n    if (b.args) |args| {\n        run_cmd.addArgs(args);\n    }\n\n    // This creates a build step. It will be visible in the `zig build --help` menu,\n    // and can be selected like this: `zig build run`\n    // This will evaluate the `run` step rather than the default, which is \"install\".\n    const run_step = b.step(\"run\", \"Run the app\");\n    run_step.dependOn(&run_cmd.step);\n\n    // Creates a step for unit testing. This only builds the test executable\n    // but does not run it.\n    const unit_tests = b.addTest(.{\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/main.zig\"),\n            .target = target,\n            .optimize = optimize,\n        }),\n    });\n    unit_tests.addIncludePath(b.path(\"src\"));\n    unit_tests.addLibraryPath(b.path(\"c\"));\n    unit_tests.installHeader(b.path(\"c/prqlc.h\"), \"prqlc.h\");\n    unit_tests.linkSystemLibrary(\"prqlc_c\");\n    unit_tests.linkLibC();\n\n    const run_unit_tests = b.addRunArtifact(unit_tests);\n\n    // Similar to creating the run step earlier, this exposes a `test` step to\n    // the `zig build --help` menu, providing a way for the user to request\n    // running the unit tests.\n    const test_step = b.step(\"test\", \"Run unit tests\");\n    test_step.dependOn(&run_unit_tests.step);\n}\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/examples/minimal-zig/src/main.zig",
    "content": "const std = @import(\"std\");\nconst prql = @cImport({\n    @cInclude(\"../c/prqlc.h\");\n});\n\npub fn main() !void {\n    var target = \"sql.mssql\".*;\n    // Setup PRQL compiler options\n    const options = prql.Options{\n        .format = false,\n        .signature_comment = false,\n        //.target = &target,\n        .target = &target\n    };\n\n    // Compile the PRQL query\n    const prql_query = \"from albums | select {album_id, title} | take 3\";\n    const result = prql.compile(prql_query, &options);\n    defer prql.result_destroy(result);\n\n    std.debug.print(\"Compiled with {d} errors\\n\", .{result.messages_len});\n    std.debug.print(\"Output:\\n\\n{s}\\n\", .{result.output});\n}\n\ntest \"simple test\" {\n    const prql_query = \"from albums | select {album_id, title} | take 3\";\n    const result = prql.compile(prql_query, null);\n    defer prql.result_destroy(result);\n    try std.testing.expect(result.messages_len == 0);\n}\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/prqlc.h",
    "content": "/*\n * PRQL is a modern language for transforming data — a simple, powerful,\n * pipelined SQL replacement\n *\n * License: Apache-2.0\n * Website: https://prql-lang.org/\n */\n\n/* This file is autogenerated. Do not modify this file manually. */\n\n#include <stdarg.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdlib.h>\n#define FFI_SCOPE \"PRQL\"\n\n/**\n * Compile message kind. Currently only Error is implemented.\n */\ntypedef enum MessageKind {\n  Error,\n  Warning,\n  Lint,\n} MessageKind;\n\n/**\n * Identifier of a location in source.\n * Contains offsets in terms of chars.\n */\ntypedef struct Span {\n  size_t start;\n  size_t end;\n} Span;\n\n/**\n * Location within a source file.\n */\ntypedef struct SourceLocation {\n  size_t start_line;\n  size_t start_col;\n  size_t end_line;\n  size_t end_col;\n} SourceLocation;\n\n/**\n * Compile result message.\n *\n * Calling code is responsible for freeing all memory allocated\n * for fields as well as strings.\n */\ntypedef struct Message {\n  /**\n   * Message kind. Currently only Error is implemented.\n   */\n  enum MessageKind kind;\n  /**\n   * Machine-readable identifier of the error\n   */\n  const char *const *code;\n  /**\n   * Plain text of the error\n   */\n  const char *reason;\n  /**\n   * A list of suggestions of how to fix the error\n   */\n  const char *const *hint;\n  /**\n   * Character offset of error origin within a source file\n   */\n  const struct Span *span;\n  /**\n   * Annotated code, containing cause and hints.\n   */\n  const char *const *display;\n  /**\n   * Line and column number of error origin within a source file\n   */\n  const struct SourceLocation *location;\n} Message;\n\n/**\n * Result of compilation.\n */\ntypedef struct CompileResult {\n  const char *output;\n  const struct Message *messages;\n  size_t messages_len;\n} CompileResult;\n\n/**\n * Compilation options\n */\ntypedef struct Options {\n  /**\n   * Pass generated SQL string through a formatter that splits it\n   * into multiple lines and prettifies indentation and spacing.\n   *\n   * Defaults to true.\n   */\n  bool format;\n  /**\n   * Target and dialect to compile to.\n   *\n   * Defaults to `sql.any`, which uses `target` argument from the query header\n   * to determine the SQL dialect.\n   */\n  char *target;\n  /**\n   * Emits the compiler signature as a comment after generated SQL\n   *\n   * Defaults to true.\n   */\n  bool signature_comment;\n} Options;\n\n/**\n * Compile a PRQL string into a SQL string.\n *\n * This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without\n * converting to JSON between each of the functions.\n *\n * See `Options` struct for available compilation options.\n *\n * # Safety\n *\n * This function assumes zero-terminated input strings.\n * Calling code is responsible for freeing memory allocated for `CompileResult`\n * by calling `result_destroy`.\n */\nstruct CompileResult compile(const char *prql_query,\n                             const struct Options *options);\n\n/**\n * Build PL AST from a PRQL string. PL in documented in the\n * [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).\n *\n * Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.\n *\n * Returns 0 on success and a negative number -1 on failure.\n *\n * # Safety\n *\n * This function assumes zero-terminated input strings.\n * Calling code is responsible for freeing memory allocated for `CompileResult`\n * by calling `result_destroy`.\n */\nstruct CompileResult prql_to_pl(const char *prql_query);\n\n/**\n * Finds variable references, validates functions calls, determines frames and\n * converts PL to RQ. PL and RQ are documented in the [prqlc Rust\n * crate](https://docs.rs/prqlc/latest/prqlc).\n *\n * Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out`\n * buffer.\n *\n * Returns 0 on success and a negative number -1 on failure.\n *\n * # Safety\n *\n * This function assumes zero-terminated input strings.\n * Calling code is responsible for freeing memory allocated for `CompileResult`\n * by calling `result_destroy`.\n */\nstruct CompileResult pl_to_rq(const char *pl_json);\n\n/**\n * Convert RQ AST into an SQL string. RQ is documented in the\n * [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).\n *\n * Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.\n *\n * Returns 0 on success and a negative number -1 on failure.\n *\n * # Safety\n *\n * This function assumes zero-terminated input strings.\n * Calling code is responsible for freeing memory allocated for `CompileResult`\n * by calling `result_destroy`.\n */\nstruct CompileResult rq_to_sql(const char *rq_json,\n                               const struct Options *options);\n\n/**\n * Destroy a `CompileResult` once you are done with it.\n *\n * # Safety\n *\n * This function expects to be called exactly once after the call of any the\n * functions that return `CompileResult`. No fields should be freed manually.\n */\nvoid result_destroy(struct CompileResult res);\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/prqlc.hpp",
    "content": "/*\n * PRQL is a modern language for transforming data — a simple, powerful,\n * pipelined SQL replacement\n *\n * License: Apache-2.0\n * Website: https://prql-lang.org/\n */\n\n/* This file is autogenerated. Do not modify this file manually. */\n\n#include <cstdarg>\n#include <cstdint>\n#include <cstdlib>\n#include <new>\n#include <ostream>\n#define FFI_SCOPE \"PRQL\"\n\nnamespace prqlc {\n\n/// Compile message kind. Currently only Error is implemented.\nenum class MessageKind {\n  Error,\n  Warning,\n  Lint,\n};\n\n/// Identifier of a location in source.\n/// Contains offsets in terms of chars.\nstruct Span {\n  size_t start;\n  size_t end;\n};\n\n/// Location within a source file.\nstruct SourceLocation {\n  size_t start_line;\n  size_t start_col;\n  size_t end_line;\n  size_t end_col;\n};\n\n/// Compile result message.\n///\n/// Calling code is responsible for freeing all memory allocated\n/// for fields as well as strings.\nstruct Message {\n  /// Message kind. Currently only Error is implemented.\n  MessageKind kind;\n  /// Machine-readable identifier of the error\n  const char *const *code;\n  /// Plain text of the error\n  const char *reason;\n  /// A list of suggestions of how to fix the error\n  const char *const *hint;\n  /// Character offset of error origin within a source file\n  const Span *span;\n  /// Annotated code, containing cause and hints.\n  const char *const *display;\n  /// Line and column number of error origin within a source file\n  const SourceLocation *location;\n};\n\n/// Result of compilation.\nstruct CompileResult {\n  const char *output;\n  const Message *messages;\n  size_t messages_len;\n};\n\n/// Compilation options\nstruct Options {\n  /// Pass generated SQL string through a formatter that splits it\n  /// into multiple lines and prettifies indentation and spacing.\n  ///\n  /// Defaults to true.\n  bool format;\n  /// Target and dialect to compile to.\n  ///\n  /// Defaults to `sql.any`, which uses `target` argument from the query header\n  /// to determine the SQL dialect.\n  char *target;\n  /// Emits the compiler signature as a comment after generated SQL\n  ///\n  /// Defaults to true.\n  bool signature_comment;\n};\n\nextern \"C\" {\n\n/// Compile a PRQL string into a SQL string.\n///\n/// This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without\n/// converting to JSON between each of the functions.\n///\n/// See `Options` struct for available compilation options.\n///\n/// # Safety\n///\n/// This function assumes zero-terminated input strings.\n/// Calling code is responsible for freeing memory allocated for `CompileResult`\n/// by calling `result_destroy`.\nCompileResult compile(const char *prql_query, const Options *options);\n\n/// Build PL AST from a PRQL string. PL in documented in the\n/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).\n///\n/// Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.\n///\n/// Returns 0 on success and a negative number -1 on failure.\n///\n/// # Safety\n///\n/// This function assumes zero-terminated input strings.\n/// Calling code is responsible for freeing memory allocated for `CompileResult`\n/// by calling `result_destroy`.\nCompileResult prql_to_pl(const char *prql_query);\n\n/// Finds variable references, validates functions calls, determines frames and\n/// converts PL to RQ. PL and RQ are documented in the [prqlc Rust\n/// crate](https://docs.rs/prqlc/latest/prqlc).\n///\n/// Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out`\n/// buffer.\n///\n/// Returns 0 on success and a negative number -1 on failure.\n///\n/// # Safety\n///\n/// This function assumes zero-terminated input strings.\n/// Calling code is responsible for freeing memory allocated for `CompileResult`\n/// by calling `result_destroy`.\nCompileResult pl_to_rq(const char *pl_json);\n\n/// Convert RQ AST into an SQL string. RQ is documented in the\n/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).\n///\n/// Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.\n///\n/// Returns 0 on success and a negative number -1 on failure.\n///\n/// # Safety\n///\n/// This function assumes zero-terminated input strings.\n/// Calling code is responsible for freeing memory allocated for `CompileResult`\n/// by calling `result_destroy`.\nCompileResult rq_to_sql(const char *rq_json, const Options *options);\n\n/// Destroy a `CompileResult` once you are done with it.\n///\n/// # Safety\n///\n/// This function expects to be called exactly once after the call of any the\n/// functions that return `CompileResult`. No fields should be freed manually.\nvoid result_destroy(CompileResult res);\n\n} // extern \"C\"\n\n} // namespace prqlc\n"
  },
  {
    "path": "prqlc/bindings/prqlc-c/src/lib.rs",
    "content": "#![cfg(not(target_family = \"wasm\"))]\n\nextern crate libc;\n\nuse std::ffi::CStr;\nuse std::ffi::CString;\nuse std::str::FromStr;\n\nuse libc::{c_char, size_t};\nuse prqlc::ErrorMessages;\nuse prqlc::Target;\n\n/// Compile a PRQL string into a SQL string.\n///\n/// This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without converting to JSON\n/// between each of the functions.\n///\n/// See `Options` struct for available compilation options.\n///\n/// # Safety\n///\n/// This function assumes zero-terminated input strings.\n/// Calling code is responsible for freeing memory allocated for `CompileResult`\n/// by calling `result_destroy`.\n#[no_mangle]\npub unsafe extern \"C\" fn compile(\n    prql_query: *const c_char,\n    options: *const Options,\n) -> CompileResult {\n    let prql_query: String = c_str_to_string(prql_query);\n\n    let options = options.as_ref().map(convert_options).transpose();\n\n    let result = options\n        .and_then(|opts| {\n            Ok(prql_query.as_str())\n                .and_then(prqlc::prql_to_pl)\n                .and_then(prqlc::pl_to_rq)\n                .and_then(|rq| prqlc::rq_to_sql(rq, &opts.unwrap_or_default()))\n        })\n        .map_err(|e| e.composed(&prql_query.into()));\n\n    result_into_c_str(result)\n}\n\n/// Build PL AST from a PRQL string. PL in documented in the\n/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).\n///\n/// Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.\n///\n/// Returns 0 on success and a negative number -1 on failure.\n///\n/// # Safety\n///\n/// This function assumes zero-terminated input strings.\n/// Calling code is responsible for freeing memory allocated for `CompileResult`\n/// by calling `result_destroy`.\n#[no_mangle]\npub unsafe extern \"C\" fn prql_to_pl(prql_query: *const c_char) -> CompileResult {\n    let prql_query: String = c_str_to_string(prql_query);\n\n    let result = Ok(prql_query.as_str())\n        .and_then(prqlc::prql_to_pl)\n        .and_then(|x| prqlc::json::from_pl(&x));\n    result_into_c_str(result)\n}\n\n/// Finds variable references, validates functions calls, determines frames and converts PL to RQ.\n/// PL and RQ are documented in the\n/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc).\n///\n/// Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out` buffer.\n///\n/// Returns 0 on success and a negative number -1 on failure.\n///\n/// # Safety\n///\n/// This function assumes zero-terminated input strings.\n/// Calling code is responsible for freeing memory allocated for `CompileResult`\n/// by calling `result_destroy`.\n#[no_mangle]\npub unsafe extern \"C\" fn pl_to_rq(pl_json: *const c_char) -> CompileResult {\n    let pl_json: String = c_str_to_string(pl_json);\n\n    let result = Ok(pl_json.as_str())\n        .and_then(prqlc::json::to_pl)\n        .and_then(prqlc::pl_to_rq)\n        .and_then(|x| prqlc::json::from_rq(&x));\n    result_into_c_str(result)\n}\n\n/// Convert RQ AST into an SQL string. RQ is documented in the\n/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).\n///\n/// Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.\n///\n/// Returns 0 on success and a negative number -1 on failure.\n///\n/// # Safety\n///\n/// This function assumes zero-terminated input strings.\n/// Calling code is responsible for freeing memory allocated for `CompileResult`\n/// by calling `result_destroy`.\n#[no_mangle]\npub unsafe extern \"C\" fn rq_to_sql(\n    rq_json: *const c_char,\n    options: *const Options,\n) -> CompileResult {\n    let rq_json: String = c_str_to_string(rq_json);\n\n    let options = options.as_ref().map(convert_options).transpose();\n\n    let result = options.and_then(|options| {\n        Ok(rq_json.as_str())\n            .and_then(prqlc::json::to_rq)\n            .and_then(|x| prqlc::rq_to_sql(x, &options.unwrap_or_default()))\n    });\n    result_into_c_str(result)\n}\n\n/// Compilation options\n#[repr(C)]\npub struct Options {\n    /// Pass generated SQL string through a formatter that splits it\n    /// into multiple lines and prettifies indentation and spacing.\n    ///\n    /// Defaults to true.\n    pub format: bool,\n\n    /// Target and dialect to compile to.\n    ///\n    /// Defaults to `sql.any`, which uses `target` argument from the query header to determine\n    /// the SQL dialect.\n    pub target: *mut c_char,\n\n    /// Emits the compiler signature as a comment after generated SQL\n    ///\n    /// Defaults to true.\n    pub signature_comment: bool,\n}\n\n/// Result of compilation.\n#[repr(C)]\npub struct CompileResult {\n    pub output: *const libc::c_char,\n    pub messages: *const Message,\n    pub messages_len: size_t,\n}\n\n/// Compile message kind. Currently only Error is implemented.\n#[repr(C)]\npub enum MessageKind {\n    Error,\n    Warning,\n    Lint,\n}\n\n/// Compile result message.\n///\n/// Calling code is responsible for freeing all memory allocated\n/// for fields as well as strings.\n// Make sure to keep in sync with prqlc::ErrorMessage\n#[repr(C)]\npub struct Message {\n    /// Message kind. Currently only Error is implemented.\n    pub kind: MessageKind,\n    /// Machine-readable identifier of the error\n    pub code: *const *const libc::c_char,\n    /// Plain text of the error\n    pub reason: *const libc::c_char,\n    /// A list of suggestions of how to fix the error\n    pub hint: *const *const libc::c_char,\n    /// Character offset of error origin within a source file\n    pub span: *const Span,\n\n    /// Annotated code, containing cause and hints.\n    pub display: *const *const libc::c_char,\n    /// Line and column number of error origin within a source file\n    pub location: *const SourceLocation,\n}\n\n/// Identifier of a location in source.\n/// Contains offsets in terms of chars.\n// Make sure to keep in sync with prqlc::Span\n#[repr(C)]\npub struct Span {\n    pub start: size_t,\n    pub end: size_t,\n}\n\n/// Location within a source file.\n// Make sure to keep in sync with prqlc::SourceLocation\n#[repr(C)]\npub struct SourceLocation {\n    pub start_line: size_t,\n    pub start_col: size_t,\n\n    pub end_line: size_t,\n    pub end_col: size_t,\n}\n\n/// Destroy a `CompileResult` once you are done with it.\n///\n/// # Safety\n///\n/// This function expects to be called exactly once after the call of any the functions\n/// that return `CompileResult`. No fields should be freed manually.\n#[no_mangle]\npub unsafe extern \"C\" fn result_destroy(res: CompileResult) {\n    // This is required because we are allocating memory for\n    // strings, vectors and options.\n    // For strings and vectors this is required, but options may be\n    // able to live entirely within the struct, instead of the heap.\n\n    for i in 0..res.messages_len {\n        let e = &*res.messages.add(i);\n\n        if !e.code.is_null() {\n            drop(CString::from_raw(*e.code as *mut libc::c_char));\n            drop(Box::from_raw(e.code as *mut *const libc::c_char));\n        }\n        drop(CString::from_raw(e.reason as *mut libc::c_char));\n        if !e.hint.is_null() {\n            drop(CString::from_raw(*e.hint as *mut libc::c_char));\n            drop(Box::from_raw(e.hint as *mut *const libc::c_char));\n        }\n        if !e.span.is_null() {\n            drop(Box::from_raw(e.span as *mut Span));\n        }\n        if !e.display.is_null() {\n            drop(CString::from_raw(*e.display as *mut libc::c_char));\n            drop(Box::from_raw(e.display as *mut *const libc::c_char));\n        }\n        if !e.location.is_null() {\n            drop(Box::from_raw(e.location as *mut SourceLocation));\n        }\n    }\n    drop(Vec::from_raw_parts(\n        res.messages as *mut i8,\n        res.messages_len,\n        res.messages_len,\n    ));\n    drop(CString::from_raw(res.output as *mut libc::c_char));\n}\n\nunsafe fn result_into_c_str(result: Result<String, ErrorMessages>) -> CompileResult {\n    match result {\n        Ok(output) => CompileResult {\n            output: convert_string(output),\n            messages: ::std::ptr::null_mut(),\n            messages_len: 0,\n        },\n        Err(err) => {\n            let mut errors = Vec::with_capacity(err.inner.len());\n            errors.extend(err.inner.into_iter().map(|e| Message {\n                kind: MessageKind::Error,\n                code: option_to_ptr(e.code.map(convert_string)),\n                reason: convert_string(e.reason),\n                hint: option_to_ptr(if e.hints.is_empty() {\n                    None\n                } else {\n                    Some(convert_string(e.hints.join(\"\\n\")))\n                }),\n                span: option_to_ptr(e.span.map(convert_span)),\n                display: option_to_ptr(e.display.map(convert_string)),\n                location: option_to_ptr(e.location.map(convert_source_location)),\n            }));\n            CompileResult {\n                output: CString::default().into_raw(),\n                messages_len: errors.len(),\n                messages: errors.leak().as_ptr(),\n            }\n        }\n    }\n}\n\n/// Allocates the value on the heap and returns a pointer to it.\n/// If the input is None, it returns null pointer.\nfn option_to_ptr<T>(o: Option<T>) -> *const T {\n    match o {\n        Some(x) => {\n            let b = Box::new(x);\n            Box::into_raw(b)\n        }\n        None => ::std::ptr::null(),\n    }\n}\n\nfn convert_string(x: String) -> *const libc::c_char {\n    CString::new(x).unwrap_or_default().into_raw()\n}\n\nfn convert_span(x: prqlc::Span) -> Span {\n    Span {\n        start: x.start,\n        end: x.end,\n    }\n}\n\nfn convert_source_location(x: prqlc::SourceLocation) -> SourceLocation {\n    SourceLocation {\n        start_line: x.start.0,\n        start_col: x.start.1,\n        end_line: x.end.0,\n        end_col: x.end.1,\n    }\n}\n\nunsafe fn c_str_to_string(c_str: *const c_char) -> String {\n    // inefficient, but simple\n    CStr::from_ptr(c_str).to_string_lossy().into_owned()\n}\n\nfn convert_options(o: &Options) -> Result<prqlc::Options, prqlc::ErrorMessages> {\n    let target = if !o.target.is_null() {\n        Some(unsafe { c_str_to_string(o.target) })\n    } else {\n        None\n    };\n    let target = target\n        .as_deref()\n        .filter(|x| !x.is_empty())\n        .unwrap_or(\"sql.any\");\n\n    let target = Target::from_str(target).map_err(prqlc::ErrorMessages::from)?;\n\n    Ok(prqlc::Options::default()\n        .with_format(o.format)\n        .with_target(target)\n        .with_signature_comment(o.signature_comment))\n}\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/.gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n.pytest_cache/\n*.py[cod]\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\n.venv/\nenv/\nbin/\nbuild/\ndevelop-eggs/\ndist/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\ninclude/\nman/\nvenv/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\npip-selfcheck.json\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\n# Rope\n.ropeproject\n\n# Django stuff:\n*.log\n*.pot\n\n# Sphinx documentation\ndocs/_build/\n\n# PyCharm\n.idea/\n\n# Pyenv\n.python-version\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/Cargo.toml",
    "content": "[package]\nbuild = \"build.rs\"\ndescription = \"Python bindings for prqlc\"\nname = \"prqlc-python\"\npublish = false\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nversion.workspace = true\n\n[lib]\nbench = false\ncrate-type = [\"cdylib\"]\ndoc = false\ndoctest = false\n\n[target.'cfg(not(target_family=\"wasm\"))'.dependencies]\npyo3 = {workspace = true}\n\n[dependencies]\n# Renamed to avoid conflicts in lib.rs\nprqlc_lib = {package = \"prqlc\", path = \"../../prqlc\", default-features = false}\n\n[dev-dependencies]\ninsta = {workspace = true}\n\n[build-dependencies]\npyo3-build-config = {workspace = true}\n\n[package.metadata.release]\ntag-name = \"{{version}}\"\ntag-prefix = \"\"\n\n# We want the package to be named `prqlc`, but the crate is named `prqlc-python`\n# to avoid a conflict with other cargo artifacts. This option renames the\n# package to `prqlc`.\n[package.metadata.maturin]\nname = \"prqlc\"\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/README.md",
    "content": "# PRQL - Python Bindings\n\nPython bindings for [PRQL](https://github.com/PRQL/prql), the Pipelined\nRelational Query Language.\n\nPRQL is a modern language for transforming data — a simple, powerful, pipelined\nSQL replacement. Like SQL, it's readable, explicit and declarative. Unlike SQL,\nit forms a logical pipeline of transformations, and supports abstractions such\nas variables and functions. It can be used with any database that uses SQL,\nsince it compiles to SQL.\n\nPRQL can be as simple as:\n\n```\nfrom tracks\nfilter artist == \"Bob Marley\"     # Each line transforms the previous result\naggregate {                       # `aggregate` reduces each column to a value\n  plays    = sum plays,\n  longest  = max length,\n  shortest = min length,          # Trailing commas are allowed\n}\n```\n\n## Installation\n\n`pip install prqlc`\n\n## Usage\n\nBasic usage:\n\n```python\nimport prqlc\n\nprql_query = \"\"\"\n    from employees\n    join salaries (==emp_id)\n    group {employees.dept_id, employees.gender} (\n      aggregate {\n        avg_salary = average salaries.salary\n      }\n    )\n\"\"\"\n\noptions = prqlc.CompileOptions(\n    format=True, signature_comment=True, target=\"sql.postgres\"\n)\n\nsql = prqlc.compile(prql_query)\nsql_postgres = prqlc.compile(prql_query, options)\n```\n\nThe following functions and classes are exposed:\n\n```python\ndef compile(prql_query: str, options: Optional[CompileOptions] = None) -> str:\n    \"\"\"Compiles a PRQL query into SQL.\"\"\"\n    ...\n\ndef prql_to_pl(prql_query: str) -> str:\n    \"\"\"Converts a PRQL query to PL AST in JSON format.\"\"\"\n    ...\n\ndef pl_to_prql(pl_json: str) -> str:\n    \"\"\"Converts PL AST as a JSON string into a formatted PRQL string.\"\"\"\n    ...\n\ndef pl_to_rq(pl_json: str) -> str:\n    \"\"\"Resolves and lowers PL AST (JSON) into RQ AST (JSON).\"\"\"\n    ...\n\ndef rq_to_sql(rq_json: str, options: Optional[CompileOptions] = None) -> str:\n    \"\"\"Converts RQ AST (JSON) into a SQL query.\"\"\"\n    ...\n\nclass CompileOptions:\n    def __init__(\n        self,\n        *,\n        format: bool = True,\n        target: str = \"sql.any\",\n        signature_comment: bool = True,\n    ) -> None:\n    \"\"\"Compilation options for SQL backend of the compiler.\n\n    Args:\n        format (bool): Pass generated SQL string through a formatter that splits\n            it into multiple lines and prettifies indentation and spacing.\n            Defaults to True.\n        target (str): Target dialect to compile to. Defaults to \"sql.any\", which\n            uses the 'target' argument from the query header to determine the\n            SQL dialect. Other targets are available by calling the `get_targets`\n            function.\n        signature_comment (bool): Emits the compiler signature as a comment after\n            the generated SQL. Defaults to True.\n\n    \"\"\"\n    ...\n\ndef get_targets() -> list[str]:\n    \"\"\"List available target dialects for compilation.\"\"\"\n    ...\n```\n\n### Debugging functions\n\nThe following functions are available within the `prqlc.debug` module. They are\nfor experimental purposes and may be unstable.\n\n```python\ndef prql_lineage(prql_query: str) -> str:\n    \"\"\"Computes a column-level lineage graph from a PRQL query.\n\n    Returns JSON-formatted string. See the docs for the `prqlc debug lineage`\n    CLI command for more details.\n    \"\"\"\n    ...\n\ndef pl_to_lineage(pl_json: str) -> str:\n    \"\"\"Computes a column-level lineage graph from PL AST (JSON).\"\"\"\n    ...\n```\n\n## Development\n\nFor development, we use `uv` for fast dependency management:\n\n```sh\n# Install development dependencies and run tests\nuv run pytest\n\n# Run type checking\nuv run mypy\n\n# Or use the task runner\ntask test\n```\n\n## Notes\n\nThese bindings are in a crate named `prqlc-python` and published to a Python\npackage on PyPI named `prqlc`, available at <https://pypi.org/project/prqlc>.\nThis crate is not published to crates.io.\n\nThe package is consumed by [pyprql](https://github.com/prql/pyprql) &\n[dbt-prql](https://github.com/prql/dbt-prql).\n\nRelies on [pyo3](https://github.com/PyO3/pyo3) for all the magic.\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/Taskfile.yaml",
    "content": "version: \"3\"\n\nvars:\n  build_dir: \"../../../target/python\"\n\ntasks:\n  build:\n    desc: Build\n    vars:\n      profile: \"dev\"\n    cmds:\n      - cmd: |\n          maturin build \\\n            --profile={{.profile}} \\\n            --out={{.build_dir}}\n\n  test:\n    desc: A fast test used for feedback during compiler development\n    cmds:\n      # uv run automatically includes dev dependencies from dependency-groups\n      - uv run pytest python/tests\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/build.rs",
    "content": "// From https://pyo3.rs/v0.14.5/building_and_distribution.html#macos\n// Note the alternative static option with `config.toml` has an problem in https://github.com/PRQL/prql/issues/411.\n\nfn main() {\n    pyo3_build_config::add_extension_module_link_args();\n}\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/noxfile.py",
    "content": "\"\"\"Nox session configuration.\"\"\"\n\nimport os\nfrom pathlib import Path\nfrom typing import List\n\nimport nox\nfrom nox.sessions import Session\n\nVERSIONS: List[str] = [\n    \"3.10\",\n    \"3.12\",\n]\n\nnox.options.stop_on_first_error = False\nnox.options.reuse_existing_virtualenvs = False\nnox.options.default_venv_backend = \"uv\"\n\n\ndef _install_prqlc(session: Session) -> None:\n    # Use uv for installation which is much faster\n    session.install(\n        \"-v\",\n        # We'd like to prevent `prqlc` from being installed from PyPI, but we do\n        # want to install its dependencies from there, and currently there's no way in\n        # plain pip of doing that (https://github.com/pypa/pip/issues/11440).\n        # \"--no-index\",\n        f\"--find-links={Path('..', '..', '..', 'target', 'python')}\",\n        \"prqlc\",\n    )\n    # Install dev dependencies separately since we're using dependency-groups\n    session.install(\"pytest>=7\", \"mypy==1.18.1\")\n\n\n@nox.session(python=VERSIONS)  # type: ignore[misc]\ndef tests(session: Session) -> None:\n    \"\"\"Run the test suite with pytest.\"\"\"\n    print(\"CWD\", os.getcwd())\n    _install_prqlc(session)\n    session.run(\"pytest\", str(Path(\"python\", \"tests\")))\n\n\n@nox.session(python=VERSIONS)  # type: ignore[misc]\ndef typing(session: Session) -> None:\n    \"\"\"Check types with mypy\"\"\"\n    _install_prqlc(session)\n    session.run(\"mypy\")\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/pyproject.toml",
    "content": "[build-system]\nbuild-backend = \"maturin\"\nrequires = [\"maturin[uv_build]>=1.7.0,<2.0\"]\n\n[project]\nclassifiers = [\n  \"Programming Language :: Rust\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n  \"Programming Language :: Python :: Implementation :: PyPy\",\n]\ndescription = \"Python bindings for prqlc, the PRQL compiler\"\ndynamic = [\"version\"]\nname = \"prqlc\"\nrequires-python = \">=3.9\"\n\n[tool.maturin]\n# This is required because of https://github.com/PyO3/pyo3/pull/2135. Instead of\n# the suggestions there to run tests with `--no-default-features`, we instead\n# disable by default and then enable in the build, given that we're going to be\n# testing more often than building.\n\n# When https://github.com/PyO3/pyo3/pull/2135 merges, we can remove this config.\n\nfeatures = [\"pyo3/extension-module\"]\n\n# The module is named `prqlc` rather than `prqlc-python`.\nmodule-name = \"prqlc\"\npython-source = \"python\"\n\n[dependency-groups]\ndev = [\n  \"pytest >= 7\",\n  \"mypy == 1.18.1\",\n]\n\n[tool.ruff]\nfix = true\nignore = [\n  # Line length — black handles\n  \"E5\", #\n  # No lambdas — too strict\n  \"E731\",\n]\n\n[tool.mypy]\nfiles = \".\"\nshow_error_codes = true\nstrict = true\nwarn_unused_ignores = true\n\n[[tool.mypy.overrides]]\nignore_missing_imports = true\nmodule = [\n  \"pytest.*\",\n  \"nox.*\",\n]\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/python/prqlc/__init__.py",
    "content": "# ruff: noqa: F403, F405\n#\n# This is the default module init provided automatically by Maturin.\nfrom .prqlc import *\n\n__doc__ = prqlc.__doc__\nif hasattr(prqlc, \"__all__\"):\n    __all__ = prqlc.__all__\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/python/prqlc/__init__.pyi",
    "content": "from typing import List, Optional\n\nclass CompileOptions:\n    def __init__(\n        self,\n        *,\n        format: bool = True,\n        target: str = \"sql.any\",\n        signature_comment: bool = True,\n    ) -> None: ...\n\ndef compile(prql_query: str, options: Optional[CompileOptions] = None) -> str: ...\ndef prql_to_pl(prql_query: str) -> str: ...\ndef pl_to_rq(pl_json: str) -> str: ...\ndef pl_to_prql(pl_json: str) -> str: ...\ndef rq_to_sql(rq_json: str) -> str: ...\ndef get_targets() -> List[str]: ...\n\n__version__: str\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/python/prqlc/debug.pyi",
    "content": "def prql_lineage(prql_query: str) -> str: ...\ndef pl_to_lineage(pl_json: str) -> str: ...\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/python/prqlc/py.typed",
    "content": ""
  },
  {
    "path": "prqlc/bindings/prqlc-python/python/tests/test_all.py",
    "content": "import json\n\nimport prqlc\n\n\ndef test_all() -> None:\n    \"\"\"\n    Test the basic python functions\n\n    Because the AST was in flux, we only test these don't throw exceptions. But we\n    should write more tests at some point.\n    \"\"\"\n\n    prql_query = \"from employee\"\n\n    res = prqlc.prql_to_pl(prql_query)\n    assert res is not None\n\n    res = prqlc.pl_to_rq(res)\n    assert res is not None\n\n    res = prqlc.rq_to_sql(res)\n    assert res is not None\n\n    assert len(prqlc.get_targets())\n\n    assert prqlc.__version__ is not None\n\n    # Example from readme\n    prql_query = \"\"\"\n        from employees\n        join salaries (==emp_id)\n        group {employees.dept_id, employees.gender} (\n            aggregate {\n                avg_salary = average salaries.salary\n            }\n        )\n    \"\"\"\n\n    options = prqlc.CompileOptions(\n        format=True, signature_comment=True, target=\"sql.postgres\"\n    )\n\n    assert prqlc.compile(prql_query)\n    assert prqlc.compile(prql_query, options)\n\n\ndef test_compile_options() -> None:\n    \"\"\"\n    Test the CompileOptions\n    \"\"\"\n    query_mssql = \"prql target:sql.mssql\\nfrom a | take 3\"\n\n    assert prqlc.compile(query_mssql).startswith(\n        \"SELECT\\n  *\\nFROM\\n  a\\nORDER BY\\n  (\\n    SELECT\\n      NULL\\n  ) OFFSET 0 ROWS\\nFETCH FIRST\\n  3 ROWS ONLY\"\n    )\n\n    options_with_known_target = prqlc.CompileOptions(\n        format=False, signature_comment=False, target=\"sql.sqlite\"\n    )\n    assert (\n        prqlc.compile(query_mssql, options_with_known_target)\n        == \"SELECT * FROM a LIMIT 3\"\n    )\n\n    options_without_target = prqlc.CompileOptions(format=False, signature_comment=False)\n    assert (\n        prqlc.compile(query_mssql, options_without_target)\n        == \"SELECT * FROM a ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH FIRST 3 ROWS ONLY\"\n    )\n\n    options_with_any_target = prqlc.CompileOptions(\n        format=False, signature_comment=False, target=\"sql.any\"\n    )\n    assert (\n        prqlc.compile(query_mssql, options_with_any_target)\n        == \"SELECT * FROM a ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH FIRST 3 ROWS ONLY\"\n    )\n\n    options_default = prqlc.CompileOptions()\n    res = prqlc.compile(query_mssql, options_default)\n    assert res.startswith(\n        \"SELECT\\n  *\\nFROM\\n  a\\nORDER BY\\n  (\\n    SELECT\\n      NULL\\n  ) OFFSET 0 ROWS\\nFETCH FIRST\\n  3 ROWS ONLY\"\n    )\n\n\ndef test_debug_functions() -> None:\n    prql_query = \"from invoices | select { id, customer_id }\"\n\n    lineage = json.loads(prqlc.debug.prql_lineage(prql_query))\n    assert lineage.keys() == {\"frames\", \"nodes\", \"ast\"}\n"
  },
  {
    "path": "prqlc/bindings/prqlc-python/src/lib.rs",
    "content": "#![cfg(not(target_family = \"wasm\"))]\nuse std::str::FromStr;\n\nuse prqlc_lib::ErrorMessages;\nuse pyo3::{exceptions, prelude::*};\n\n#[pyfunction]\n#[pyo3(signature = (prql_query, options=None))]\npub fn compile(prql_query: &str, options: Option<CompileOptions>) -> PyResult<String> {\n    let Ok(options) = options.map(convert_options).transpose() else {\n        return Err(PyErr::new::<exceptions::PyValueError, _>(\n            \"Invalid options\".to_string(),\n        ));\n    };\n\n    prqlc_lib::compile(prql_query, &options.unwrap_or_default())\n        .map_err(|err| PyErr::new::<exceptions::PyValueError, _>(err.to_string()))\n}\n\n#[pyfunction]\npub fn prql_to_pl(prql_query: &str) -> PyResult<String> {\n    prqlc_lib::prql_to_pl(prql_query)\n        .and_then(|x| prqlc_lib::json::from_pl(&x))\n        .map_err(|err| PyErr::new::<exceptions::PyValueError, _>(err.to_json()))\n}\n\n#[pyfunction]\npub fn pl_to_prql(pl_json: &str) -> PyResult<String> {\n    prqlc_lib::json::to_pl(pl_json)\n        .and_then(|x| prqlc_lib::pl_to_prql(&x))\n        .map_err(|err| PyErr::new::<exceptions::PyValueError, _>(err.to_json()))\n}\n\n#[pyfunction]\npub fn pl_to_rq(pl_json: &str) -> PyResult<String> {\n    prqlc_lib::json::to_pl(pl_json)\n        .and_then(prqlc_lib::pl_to_rq)\n        .and_then(|x| prqlc_lib::json::from_rq(&x))\n        .map_err(|err| PyErr::new::<exceptions::PyValueError, _>(err.to_json()))\n}\n\n#[pyfunction]\n#[pyo3(signature = (rq_json, options=None))]\npub fn rq_to_sql(rq_json: &str, options: Option<CompileOptions>) -> PyResult<String> {\n    prqlc_lib::json::to_rq(rq_json)\n        .and_then(|x| {\n            prqlc_lib::rq_to_sql(\n                x,\n                &options\n                    .map(convert_options)\n                    .transpose()?\n                    .unwrap_or_default(),\n            )\n        })\n        .map_err(|err| PyErr::new::<exceptions::PyValueError, _>(err.to_json()))\n}\n\nmod debug {\n    use super::*;\n\n    #[pyfunction]\n    pub fn prql_lineage(prql_query: &str) -> PyResult<String> {\n        prqlc_lib::prql_to_pl(prql_query)\n            .and_then(prqlc_lib::internal::pl_to_lineage)\n            .and_then(|x| prqlc_lib::internal::json::from_lineage(&x))\n            .map_err(|err| PyErr::new::<exceptions::PyValueError, _>(err.to_json()))\n    }\n\n    #[pyfunction]\n    pub fn pl_to_lineage(pl_json: &str) -> PyResult<String> {\n        prqlc_lib::json::to_pl(pl_json)\n            .and_then(prqlc_lib::internal::pl_to_lineage)\n            .and_then(|x| prqlc_lib::internal::json::from_lineage(&x))\n            .map_err(|err| PyErr::new::<exceptions::PyValueError, _>(err.to_json()))\n    }\n}\n\n#[pymodule]\nfn prqlc(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {\n    m.add_function(wrap_pyfunction!(compile, m)?)?;\n    m.add_function(wrap_pyfunction!(prql_to_pl, m)?)?;\n    m.add_function(wrap_pyfunction!(pl_to_prql, m)?)?;\n    m.add_function(wrap_pyfunction!(pl_to_rq, m)?)?;\n    m.add_function(wrap_pyfunction!(rq_to_sql, m)?)?;\n    m.add_function(wrap_pyfunction!(get_targets, m)?)?;\n\n    m.add_class::<CompileOptions>()?;\n    // From https://github.com/PyO3/maturin/issues/100\n    m.add(\"__version__\", env!(\"CARGO_PKG_VERSION\"))?;\n\n    // add debug submodule\n    let debug_module = PyModule::new(_py, \"debug\")?;\n    debug_module.add_function(wrap_pyfunction!(debug::prql_lineage, &debug_module)?)?;\n    debug_module.add_function(wrap_pyfunction!(debug::pl_to_lineage, &debug_module)?)?;\n\n    m.add_submodule(&debug_module)?;\n\n    Ok(())\n}\n\n/// Compilation options for SQL backend of the compiler.\n#[pyclass]\n#[derive(Clone, Debug)]\npub struct CompileOptions {\n    /// Pass generated SQL string through a formatter that splits it into\n    /// multiple lines and prettifies indentation and spacing.\n    ///\n    /// Defaults to true.\n    pub format: bool,\n\n    /// Target to compile to.\n    ///\n    /// Defaults to \"sql.any\", which uses the `target` argument from the query\n    /// header to determine The SQL dialect.\n    pub target: String,\n\n    /// Emits the compiler signature as a comment after generated SQL\n    ///\n    /// Defaults to true.\n    pub signature_comment: bool,\n\n    pub color: bool,\n\n    pub display: String,\n}\n\n#[pymethods]\nimpl CompileOptions {\n    #[new]\n    #[pyo3(signature = (*, format=true, signature_comment=true, target=\"sql.any\".to_string(), color=false, display=\"plain\".to_string()))]\n    pub fn new(\n        format: bool,\n        signature_comment: bool,\n        target: String,\n        color: bool,\n        display: String,\n    ) -> Self {\n        CompileOptions {\n            format,\n            target,\n            signature_comment,\n            color,\n            display: display.to_lowercase(),\n        }\n    }\n}\n\nfn convert_options(o: CompileOptions) -> Result<prqlc_lib::Options, prqlc_lib::ErrorMessages> {\n    use prqlc_lib::Error;\n    let target = prqlc_lib::Target::from_str(&o.target).map_err(prqlc_lib::ErrorMessages::from)?;\n\n    Ok(prqlc_lib::Options {\n        format: o.format,\n        target,\n        signature_comment: o.signature_comment,\n        color: false,\n        display: prqlc_lib::DisplayOptions::from_str(&o.display).map_err(|e| ErrorMessages {\n            inner: vec![Error::new_simple(format!(\"Invalid display option: {e}\")).into()],\n        })?,\n    })\n}\n\n#[pyfunction]\npub fn get_targets() -> Vec<String> {\n    prqlc_lib::Target::names()\n}\n\n#[cfg(test)]\nmod test {\n    use insta::assert_snapshot;\n\n    use super::*;\n\n    #[test]\n    fn parse_for_python() {\n        let opts = Some(CompileOptions {\n            format: true,\n            target: \"sql.any\".to_string(),\n            signature_comment: false,\n            color: false,\n            display: \"plain\".to_string(),\n        });\n\n        assert_snapshot!(\n            compile(\"from employees | filter (age | in 20..30)\", opts).unwrap(),\n            @r\"\n        SELECT\n          *\n        FROM\n          employees\n        WHERE\n          age BETWEEN 20 AND 30\n        \"\n        );\n    }\n\n    #[test]\n    fn parse_pipeline() {\n        let opts = Some(CompileOptions {\n            format: true,\n            target: \"sql.any\".to_string(),\n            signature_comment: false,\n            color: false,\n            display: \"plain\".to_string(),\n        });\n\n        let prql = r#\"from artists | select {name, id} | filter (id | in [1, 2, 3])\"#;\n        assert_snapshot!(\n             prql_to_pl(prql).and_then(|x| pl_to_rq(x.as_str())).and_then(|x|rq_to_sql(x.as_str(), opts)).unwrap(), @r\"\n        SELECT\n          name,\n          id\n        FROM\n          artists\n        WHERE\n          id IN (1, 2, 3)\n        \");\n    }\n\n    #[test]\n    fn prql_pl_prql_roundtrip() {\n        let prql = r#\"from artists | select {name, id} | filter (id | in [1, 2, 3])\"#;\n        assert_snapshot!(\n             prql_to_pl(prql).and_then(|x| pl_to_prql(x.as_str())).unwrap(), @r\"\n        from artists\n        select {name, id}\n        filter (id | in [1, 2, 3])\n        \");\n    }\n\n    #[test]\n    fn debug_prql_lineage() {\n        assert_snapshot!(\n            debug::prql_lineage(r#\"from a | select { beta, gamma }\"#).unwrap(),\n            @r#\"{\"frames\":[[\"1:9-31\",{\"columns\":[{\"Single\":{\"name\":[\"a\",\"beta\"],\"target_id\":118,\"target_name\":null}},{\"Single\":{\"name\":[\"a\",\"gamma\"],\"target_id\":119,\"target_name\":null}}],\"inputs\":[{\"id\":116,\"name\":\"a\",\"table\":[\"default_db\",\"a\"]}]}]],\"nodes\":[{\"id\":116,\"kind\":\"Ident\",\"span\":\"1:0-6\",\"ident\":{\"Ident\":[\"default_db\",\"a\"]},\"parent\":121},{\"id\":118,\"kind\":\"Ident\",\"span\":\"1:18-22\",\"ident\":{\"Ident\":[\"this\",\"a\",\"beta\"]},\"targets\":[116],\"parent\":120},{\"id\":119,\"kind\":\"Ident\",\"span\":\"1:24-29\",\"ident\":{\"Ident\":[\"this\",\"a\",\"gamma\"]},\"targets\":[116],\"parent\":120},{\"id\":120,\"kind\":\"Tuple\",\"span\":\"1:16-31\",\"children\":[118,119],\"parent\":121},{\"id\":121,\"kind\":\"TransformCall: Select\",\"span\":\"1:9-31\",\"children\":[116,120]}],\"ast\":{\"name\":\"Project\",\"stmts\":[{\"VarDef\":{\"kind\":\"Main\",\"name\":\"main\",\"value\":{\"Pipeline\":{\"exprs\":[{\"FuncCall\":{\"name\":{\"Ident\":[\"from\"],\"span\":\"1:0-4\"},\"args\":[{\"Ident\":[\"a\"],\"span\":\"1:5-6\"}]},\"span\":\"1:0-6\"},{\"FuncCall\":{\"name\":{\"Ident\":[\"select\"],\"span\":\"1:9-15\"},\"args\":[{\"Tuple\":[{\"Ident\":[\"beta\"],\"span\":\"1:18-22\"},{\"Ident\":[\"gamma\"],\"span\":\"1:24-29\"}],\"span\":\"1:16-31\"}]},\"span\":\"1:9-31\"}]},\"span\":\"1:0-31\"}},\"span\":\"1:0-31\"}]}}\"#\n        );\n    }\n\n    #[test]\n    fn debug_pl_to_lineage() {\n        assert_snapshot!(\n            prql_to_pl(r#\"from a | select { beta, gamma }\"#).and_then(|x| debug::pl_to_lineage(&x)).unwrap(),\n            @r#\"{\"frames\":[[\"1:9-31\",{\"columns\":[{\"Single\":{\"name\":[\"a\",\"beta\"],\"target_id\":118,\"target_name\":null}},{\"Single\":{\"name\":[\"a\",\"gamma\"],\"target_id\":119,\"target_name\":null}}],\"inputs\":[{\"id\":116,\"name\":\"a\",\"table\":[\"default_db\",\"a\"]}]}]],\"nodes\":[{\"id\":116,\"kind\":\"Ident\",\"span\":\"1:0-6\",\"ident\":{\"Ident\":[\"default_db\",\"a\"]},\"parent\":121},{\"id\":118,\"kind\":\"Ident\",\"span\":\"1:18-22\",\"ident\":{\"Ident\":[\"this\",\"a\",\"beta\"]},\"targets\":[116],\"parent\":120},{\"id\":119,\"kind\":\"Ident\",\"span\":\"1:24-29\",\"ident\":{\"Ident\":[\"this\",\"a\",\"gamma\"]},\"targets\":[116],\"parent\":120},{\"id\":120,\"kind\":\"Tuple\",\"span\":\"1:16-31\",\"children\":[118,119],\"parent\":121},{\"id\":121,\"kind\":\"TransformCall: Select\",\"span\":\"1:9-31\",\"children\":[116,120]}],\"ast\":{\"name\":\"Project\",\"stmts\":[{\"VarDef\":{\"kind\":\"Main\",\"name\":\"main\",\"value\":{\"Pipeline\":{\"exprs\":[{\"FuncCall\":{\"name\":{\"Ident\":[\"from\"],\"span\":\"1:0-4\"},\"args\":[{\"Ident\":[\"a\"],\"span\":\"1:5-6\"}]},\"span\":\"1:0-6\"},{\"FuncCall\":{\"name\":{\"Ident\":[\"select\"],\"span\":\"1:9-15\"},\"args\":[{\"Tuple\":[{\"Ident\":[\"beta\"],\"span\":\"1:18-22\"},{\"Ident\":[\"gamma\"],\"span\":\"1:24-29\"}],\"span\":\"1:16-31\"}]},\"span\":\"1:9-31\"}]},\"span\":\"1:0-31\"}},\"span\":\"1:0-31\"}]}}\"#\n        );\n    }\n}\n"
  },
  {
    "path": "prqlc/packages/snap/snapcraft.yaml",
    "content": "name: prqlc\ntitle: PRQL Compiler\nbase: core22\nversion: \"0.13.12\"\nsummary: CLI for PRQL, a modern language for transforming data\ndescription: |\n  prqlc is the CLI for the PRQL compiler. It compiles PRQL to SQL, and offers various diagnostics.\n\n  PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement.\nissues: https://github.com/PRQL/prql/issues\nsource-code: https://github.com/PRQL/prql\ncontact: https://twitter.com/prql_lang\nwebsite: https://prql-lang.org/\nlicense: Apache-2.0\ngrade: stable\nconfinement: strict\nicon: web/website/static/img/icon.svg\n\nparts:\n  rust-deps:\n    plugin: nil\n    override-pull:\n      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n      --profile minimal --default-toolchain 1.69.0\n  prqlc:\n    plugin: rust\n    after: [rust-deps]\n    source: .\n    build-packages: [cargo]\n    rust-path: [prqlc/prqlc]\n\napps:\n  prqlc:\n    command: bin/prqlc\n    plugs:\n      - home\n"
  },
  {
    "path": "prqlc/prqlc/ARCHITECTURE.md",
    "content": "# prqlc Architecture\n\nThe PRQL compiler operates in the following stages:\n\n| stage    | sub-stage    | Abstract Syntax Tree (AST) Type used  |\n| -------- | ------------ | ------------------------------------- |\n| parse    | lexer        | string -> _LR — Lexer Representation_ |\n| parse    | parser       | LR -> _PR — Parser Representation_    |\n| semantic | ast_expand   | PR -> _PL — Pipelined Language_       |\n| semantic | resolver     | PL                                    |\n| semantic | flatten      | PL                                    |\n| semantic | lowering     | PL -> _RQ — Resolved Query_           |\n| sql      | preprocess   | RQ                                    |\n| sql      | pq-compiler  | RQ -> _PQ — Partitioned Query_        |\n| sql      | postprocess  | PQ                                    |\n| sql      | sql-compiler | PQ -> `sqlparser::ast`                |\n| sql      | codegen      | `sqlparser::ast` -> string            |\n\n1. **Lexing & Parsing**: PRQL source text is split into tokens with the Chumsky\n   parser named \"lexer\". The stream of tokens, as Lexer Representation (LR), is\n   then parsed into an Abstract Syntax Tree (AST) called Parser Representation\n   (PR).\n\n2. **Semantic Analysis**: This stage resolves names (identifiers), extracts\n   declarations, and determines frames (table columns in each step). A `Context`\n   is declared containing the root module, which maps accessible names to their\n   declarations.\n\n   The resolving process involves the following operations:\n   - Assign an ID to each node (`Expr` and `Stmt`).\n   - Extract function declarations and variable definitions into the appropriate\n     `Module`, accessible from `Context::root_mod`.\n   - Look up identifiers in the module and find the associated declaration. The\n     identifier is replaced with a fully qualified name that guarantees a unique\n     name in `root_mod`. In some cases, `Expr::target` is also set.\n   - Convert function calls to transforms (`from`, `derive`, `filter`) from\n     `FuncCall` to `TransformCall`, which is more convenient for later\n     processing.\n   - Determine the type of expressions. If an expression is a reference to a\n     table, use the frame of the table as the type. If it is a `TransformCall`,\n     apply the transform to the input frame to obtain the resulting type. For\n     simple expressions, try to infer from `ExprKind`.\n   - Lowering: This stage converts the PL into RQ, which is more strictly typed\n     and contains less information but is convenient for translating into SQL or\n     other backends.\n\n3. **SQL Backend**: This stage converts RQ into PQ, an intermediate AST, before\n   finally converting to SQL. Each relation is transformed into an SQL query.\n   Pipelines are analyzed and split into \"AtomicPipelines\" at appropriate\n   positions, which can be represented by a single SELECT statement.\n\n   Splitting is performed back-to-front. First, a list of all output columns is\n   created. The pipeline is then traversed backwards, and splitting occurs when\n   an incompatible transform with those already present in the pipeline is\n   encountered. Splitting can also be triggered by encountering an expression\n   that cannot be materialized where it is used (e.g., a window function in a\n   WHERE clause).\n\n   This process is also called anchoring, as it anchors a column definition to a\n   specific location in the output query.\n\n   During this process, `sql::context` keeps track of:\n   - Table instances in the query (to prevent mixing up multiple instances of\n     the same table)\n   - Column definitions, whether computed or a reference to a table column\n   - Column names, as defined in RQ or generated\n"
  },
  {
    "path": "prqlc/prqlc/Cargo.toml",
    "content": "[package]\ndescription = \"PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement.\"\nname = \"prqlc\"\nautobenches = false\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nversion.workspace = true\n\n# Required for `cargo-msrv`, which doesn't yet support workspaces\nmetadata.msrv = \"1.75.0\"\n\nbuild = \"build.rs\"\n\n[features]\ncli = [\n  \"anyhow\",\n  \"clap_complete_command\",\n  \"clap\",\n  \"clio\",\n  \"color-eyre\",\n  \"colorchoice-clap\",\n  \"is-terminal\",\n  \"minijinja\",\n  \"notify\",\n  \"serde_yaml\",\n  \"walkdir\",\n]\ndefault = [\"cli\"]\nlsp = [\"lsp-server\", \"lsp-types\"] # Just a stub without any real functionality\nserde_yaml = [\"prqlc-parser/serde_yaml\", \"dep:serde_yaml\"]\ntest-dbs = [\n  \"rusqlite\",\n  \"duckdb\",\n  \"connector_arrow\",\n  \"connector_arrow/src_rusqlite\",\n  \"connector_arrow/src_duckdb\",\n]\ntest-dbs-external = [\n  # We could attempt to allow running `test-dbs-external` without `test-dbs`,\n  # but it doesn't offer much value, and it's simpler to require both.\n  \"test-dbs\",\n  \"postgres\",\n  \"mysql\",\n  \"tiberius\",\n  \"futures\",\n  \"tokio\",\n  \"tokio-util\",\n  \"connector_arrow/src_mysql\",\n  \"connector_arrow/src_postgres\",\n  \"connector_arrow/src_tiberius\",\n]\n\n[dependencies]\nprqlc-parser = { path = \"../prqlc-parser\", version = \"0.13.12\" }\n\nanstream = { version = \"1.0.0\", features = [\"auto\"] }\nariadne = \"0.5.1\"\nchrono = \"0.4.44\"\ncsv = \"1.4.0\"\nenum-as-inner = { workspace = true }\nitertools = { workspace = true }\nlog = { workspace = true }\nregex = \"1.12.3\"\nschemars = { workspace = true }\nsemver = { workspace = true }\n# serde is required for the `from_text` feature of PRQL, so we can't really put\n# it behind a feature.\nserde = { workspace = true }\nserde_json = { workspace = true }\nserde_yaml = { workspace = true, optional = true }\nsqlformat = \"0.3.5\"\nsqlparser = { version = \"0.60.0\", features = [\n  \"serde\",\n  # enabling \"recursive-protection\" causes wasm compilation to fail on MacOS\n], default-features = false }\nstrum = { workspace = true }\nstrum_macros = { workspace = true }\nlsp-server = { version = \"0.7.9\", optional = true }\nlsp-types = { version = \"0.97.0\", optional = true }\n\n[build-dependencies]\nvergen-gitcl = { version = \"1.0.0\", features = [\"build\"] }\n\n[target.'cfg(not(target_family=\"wasm\"))'.dependencies]\n\n# unique dependencies from the CLI, marked as optional and included in the 'cli'\n# feature\nanyhow = { version = \"1.0.102\", features = [\"backtrace\"], optional = true }\nclap = { version = \"4.5.53\", features = [\n  \"derive\",\n  \"env\",\n  \"wrap_help\",\n], optional = true }\nclap_complete_command = { version = \"0.5.1\", optional = true }\nclio = { version = \"0.3.3\", features = ['clap-parse'], optional = true }\ncolor-eyre = { version = \"0.6.5\", optional = true }\ncolorchoice-clap = { version = \"1.0.0\", optional = true }\nis-terminal = { version = \"0.4.17\", optional = true }\nnotify = { version = \"7.0.0\", optional = true }\nwalkdir = { version = \"2.5.0\", optional = true }\n\n# We use minijinja just for the Jinja lexer, which is not part of the\n# public interface which is covered by semver guarantees.\nminijinja = { version = \"2.15.1\", features = [\n  \"unstable_machinery\",\n], optional = true }\n\n# For integration tests. These are gated by the `test-dbs` and `test-dbs-external` features,\n# rather than dev-dependencies, because dev-dependencies can't be optional.\n\nconnector_arrow = { version = \"0.10.0\", optional = true }\nduckdb = { version = \"1.4.4\", optional = true, features = [\n  \"bundled\",\n  \"chrono\",\n] }\nfutures = { version = \"0.3.32\", optional = true }\nmysql = { version = \"27\", optional = true }\npostgres = { version = \"0.19.10\", optional = true }\nrusqlite = { version = \"0.38.0\", optional = true, features = [\n  \"bundled\",\n  \"csvtab\",\n] }\ntiberius = { version = \"0.12.3\", optional = true, default-features = false, features = [\n  \"sql-browser-tokio\",\n  \"bigdecimal\",\n  \"time\",\n  \"rustls\",\n  \"tds73\",\n] }\ntokio = { version = \"1.50.0\", optional = true, features = [\"full\"] }\ntokio-util = { version = \"0.7.18\", optional = true, features = [\"compat\"] }\n\n[dev-dependencies]\nglob = { version = \"0.3.3\" }\ninsta = { workspace = true }\ninsta-cmd = { workspace = true }\nrstest = \"0.26.1\"\nsimilar = { workspace = true }\nsimilar-asserts = { workspace = true }\ntempfile = { version = \"3.27.0\" }\ntest_each_file = \"0.3.7\"\n\n# criterion 0.8 depends on `alloca` which doesn't support WASM targets\n[target.'cfg(not(target_family = \"wasm\"))'.dev-dependencies]\ncriterion = { version = \"0.8.2\", default-features = false }\n\n# We use `benches/bench.rs` for the benchmark harness so disable searching for\n# benchmarks in bin & lib here to simplify using criterion\n# https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options\n[lib]\nbench = false\n[[bin]]\nbench = false\nname = \"prqlc\"\n[[bench]]\nharness = false\nname = \"bench\"\n\n# Putting these pre-release replacements in the workspace root causes it to\n# refer to the path relative to `prqlc`, so we choose this crate out of\n# default.\n\n[[package.metadata.release.pre-release-replacements]]\nexactly = 1\nfile = \"../packages/snap/snapcraft.yaml\"\nreplace = 'version: \"{{version}}\"'\nsearch = '^version: \"[\\d.]+\"$'\n\n[[package.metadata.release.pre-release-replacements]]\nexactly = 1\nfile = \"../../web/book/src/project/target.md\"\n# This should be on the previous version, since otherwise it won't be able to\n# compile during development, since we use the most recent tag during\n# development (and the most recent tag is the prior version).\nreplace = 'prql version:\"{{prev_version}}\"'\nsearch = 'prql version:\"[\\d.]+\"'\n\n[[package.metadata.release.pre-release-replacements]]\nexactly = 1\nfile = \"../../CHANGELOG.md\"\nreplace = \"# PRQL Changelog\\n\\n## [unreleased]\\n\\n**Language**:\\n\\n**Features**:\\n\\n**Fixes**:\\n\\n**Documentation**:\\n\\n**Web**:\\n\\n**Integrations**:\\n\\n**Internal changes**:\\n\\n**New Contributors**:\\n\\n\"\nsearch = \"# PRQL Changelog\\n\\n\"\n\n[lints.rust]\nunsafe_code = \"forbid\"\n\n[lints.clippy]\n# Our error type is 128 bytes, because it contains 5 strings & an Enum, which\n# is exactly the default warning level. Given we're not that performance\n# sensitive, it's fine to ignore this at the moment (and not worth having a\n# clippy config file for a single setting). We can consider adjusting it as a\n# yak-shaving exercise in the future.\nresult_large_err = \"allow\"\n"
  },
  {
    "path": "prqlc/prqlc/README.md",
    "content": "# PRQL compiler\n\n`prqlc` is the reference implementation of a compiler from PRQL to SQL, written\nin Rust. It also serves as the CLI.\n\nFor more on PRQL, check out the [PRQL website](https://prql-lang.org) or the\n[PRQL repo](https://github.com/PRQL/prql).\n\n## CLI\n\n`prqlc` serves as a CLI for the PRQL compiler. It is a single, dependency-free\nbinary that compiles PRQL into SQL.\n\n## Usage\n\n### `prqlc compile`\n\nThis command works as a filter that compiles a PRQL string into an SQL string.\n\n```sh\n$ echo 'from employees | filter has_dog | select salary' | prqlc compile\n\nSELECT\n  salary\nFROM\n  employees\nWHERE\n  has_dog\n```\n\nA PRQL query can be executed with CLI tools compatible with SQL, such as\n[DuckDB CLI](https://duckdb.org/docs/api/cli.html).\n\n```sh\n$ curl -fsL https://raw.githubusercontent.com/PRQL/prql/0.12.2/prqlc/prqlc/tests/integration/data/chinook/albums.csv -o albums.csv\n$ echo 'from `albums.csv` | take 3' | prqlc compile | duckdb\n┌──────────┬───────────────────────────────────────┬───────────┐\n│ album_id │                 title                 │ artist_id │\n│  int64   │                varchar                │   int64   │\n├──────────┼───────────────────────────────────────┼───────────┤\n│        1 │ For Those About To Rock We Salute You │         1 │\n│        2 │ Balls to the Wall                     │         2 │\n│        3 │ Restless and Wild                     │         2 │\n└──────────┴───────────────────────────────────────┴───────────┘\n```\n\nExecuting this command without any argument will start interactive mode,\nallowing a PRQL query to be written interactively. In this mode, after writing\nPRQL and press `Ctrl-d` (Linux, macOS) or `Ctrl-z` (Windows) to display the\ncompiled SQL.\n\n```sh\nprqlc compile\n```\n\nJust like when using it as a filter, SQL string output can be passed to the\nDuckDB CLI and similar tools.\n\n```sh\n$ prqlc compile | duckdb\nEnter PRQL, then press ctrl-d to compile:\n\nfrom `albums.csv`\ntake 3\n┌──────────┬───────────────────────────────────────┬───────────┐\n│ album_id │                 title                 │ artist_id │\n│  int64   │                varchar                │   int64   │\n├──────────┼───────────────────────────────────────┼───────────┤\n│        1 │ For Those About To Rock We Salute You │         1 │\n│        2 │ Balls to the Wall                     │         2 │\n│        3 │ Restless and Wild                     │         2 │\n└──────────┴───────────────────────────────────────┴───────────┘\n```\n\n## Installation\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/prqlc.svg)](https://repology.org/project/prqlc/versions)\n\n### via Homebrew (macOS, Linux)\n\n```sh\nbrew install prqlc\n```\n\n### via winget (Windows)\n\n```sh\nwinget install prqlc\n```\n\n### From GitHub release page\n\nPrecompiled binaries are available for Linux, macOS, and Windows on the\n[PRQL release page](https://github.com/PRQL/prql/releases).\n\n### From source\n\n```sh\n# From crates.io\ncargo install prqlc\n```\n\n```sh\n# From a local PRQL repository\ncargo install --path prqlc/prqlc\n```\n\n### Shell completions\n\nThe `prqlc shell-completion` command prints a shell completion script for\nsupported shells, and saving the printed scripts to files makes for shells to\nload completions for each session.\n\n#### Bash\n\nFor Linux:\n\n```sh\nprqlc shell-completion bash >/etc/bash_completion.d/prqlc\n```\n\nFor macOS:\n\n```sh\nprqlc shell-completion bash >/usr/local/etc/bash_completion.d/prqlc\n```\n\n#### fish\n\n```sh\nprqlc shell-completion fish >~/.config/fish/completions/prqlc.fish\n```\n\n#### PowerShell\n\n```powershell\nmkdir -Path (Split-Path -Parent $profile) -ErrorAction SilentlyContinue\nprqlc shell-completion powershell >path/to/prqlc.ps1\necho 'Invoke-Expression -Command path/to/prqlc.ps1' >>$profile\n```\n\n#### zsh\n\n```sh\nprqlc shell-completion zsh >\"${fpath[1]}/_prqlc\"\n```\n\nEnsure that the following lines are present in `~/.zshrc`:\n\n```sh\nautoload -U compinit\ncompinit -i\n```\n\n## Helpers\n\nCheat sheets for `prqlc` are available on various websites and with various\ntools.\n\n- [`tldr`](https://tldr.sh/)\n  ([on the web](https://tldr.inbrowser.app/pages/common/prqlc))\n- [`eg`](https://github.com/srsudar/eg)\n\n<!-- Issues: #2034 cheat/cheatsheets, #2041 devhints.io -->\n\n## Library\n\nFor more usage examples and the library documentation, check out the\n[`prqlc` documentation](https://docs.rs/prqlc/).\n\n### Library installation\n\n```shell\ncargo add prqlc\n```\n\n### Examples\n\nCompile a PRQL string to a SQLite dialect string:\n\n```rust\n// In a file src/main.rs\n\nuse prqlc::{compile, Options, DisplayOptions, Target, sql::Dialect};\n\nlet prql = \"from employees | select {name, age}\";\nlet opts = &Options {\n    format: false,\n    target: Target::Sql(Some(Dialect::SQLite)),\n    signature_comment: false,\n    display: DisplayOptions::Plain,\n    ..Default::default()\n};\nlet sql = compile(&prql, opts).unwrap();\nassert_eq!(\"SELECT name, age FROM employees\", sql);\n```\n"
  },
  {
    "path": "prqlc/prqlc/benches/bench.rs",
    "content": "// Exclude benchmarks from WASM builds (criterion depends on alloca).\n// The inner cfg attr compiles an empty crate, but we still need a main for WASM.\n#![cfg_attr(not(target_family = \"wasm\"), allow(unused))]\n\n#[cfg(target_family = \"wasm\")]\nfn main() {}\n\n#[cfg(not(target_family = \"wasm\"))]\ninclude!(\"bench_impl.rs\");\n"
  },
  {
    "path": "prqlc/prqlc/benches/bench_impl.rs",
    "content": "use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};\nuse glob::glob;\nuse prqlc::{compile, pl_to_prql, pl_to_rq, prql_to_pl, Options};\nuse std::collections::BTreeMap;\nuse std::fs;\n\ntype Queries = BTreeMap<String, String>;\n\nfn load_queries() -> Queries {\n    glob(\"tests/integration/queries/**/*.prql\")\n        .unwrap()\n        .filter_map(|entry| {\n            let path = entry.ok()?;\n            let name = path.file_stem()?.to_string_lossy().into_owned();\n            let content = fs::read_to_string(&path).ok()?;\n            Some((name, content))\n        })\n        .collect()\n}\n\nfn bench_compile(c: &mut Criterion) {\n    let queries = load_queries();\n    let options = Options::default();\n    let mut group = c.benchmark_group(\"compile\");\n\n    for (name, content) in queries.iter() {\n        group.bench_with_input(BenchmarkId::from_parameter(name), content, |b, content| {\n            b.iter(|| compile(content, &options));\n        });\n    }\n    group.finish();\n}\n\nfn bench_prql_to_pl(c: &mut Criterion) {\n    let queries = load_queries();\n    let mut group = c.benchmark_group(\"prql_to_pl\");\n\n    for (name, content) in queries.iter() {\n        group.bench_with_input(BenchmarkId::from_parameter(name), content, |b, content| {\n            b.iter(|| prql_to_pl(content));\n        });\n    }\n    group.finish();\n}\n\nfn bench_pl_to_rq(c: &mut Criterion) {\n    let queries = load_queries();\n    let mut group = c.benchmark_group(\"pl_to_rq\");\n\n    for (name, content) in queries.iter() {\n        let pl = prql_to_pl(content).unwrap();\n        group.bench_with_input(BenchmarkId::from_parameter(name), &pl, |b, pl| {\n            b.iter(|| pl_to_rq(pl.clone()));\n        });\n    }\n    group.finish();\n}\n\nfn bench_pl_to_prql(c: &mut Criterion) {\n    let queries = load_queries();\n    let mut group = c.benchmark_group(\"pl_to_prql\");\n\n    for (name, content) in queries.iter() {\n        let pl = prql_to_pl(content).unwrap();\n        group.bench_with_input(BenchmarkId::from_parameter(name), &pl, |b, pl| {\n            b.iter(|| pl_to_prql(pl));\n        });\n    }\n    group.finish();\n}\n\ncriterion_group!(\n    benches,\n    bench_compile,\n    bench_prql_to_pl,\n    bench_pl_to_rq,\n    bench_pl_to_prql\n);\ncriterion_main!(benches);\n"
  },
  {
    "path": "prqlc/prqlc/build.rs",
    "content": "use std::error::Error;\n// gix failing on https://github.com/rustyhorde/vergen/issues/359, and `git2`\n// fails on `aarch64` so we're using `gitcl`. Switch to `gitx` when that bug is\n// fixed.\nuse vergen_gitcl::{Emitter, GitclBuilder as GitBuilder};\n\npub fn main() -> Result<(), Box<dyn Error>> {\n    let git = GitBuilder::default().describe(true, true, None).build()?;\n    Emitter::default().add_instructions(&git)?.emit()?;\n    Ok(())\n}\n"
  },
  {
    "path": "prqlc/prqlc/examples/compile-files/Cargo.toml",
    "content": "[package]\nname = \"compile-files\"\npublish = false\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nversion.workspace = true\n\n[[bin]]\nbench = false\ndoc = false\ndoctest = false\nname = \"compile-files\"\ntest = false\n\n[build-dependencies]\nprqlc = {path = '../../../prqlc', default-features = false}\n"
  },
  {
    "path": "prqlc/prqlc/examples/compile-files/README.md",
    "content": "# Compile-time example\n\n`compile-files` is an example of using `prqlc` to compile PRQL to SQL at compile\ntime in a Rust crate.\n"
  },
  {
    "path": "prqlc/prqlc/examples/compile-files/build.rs",
    "content": "use std::{env, fs, path::Path};\n\nuse prqlc::{compile, Options};\n\nfn main() {\n    // we expect queries to reside in `queries/` dir\n    let paths = fs::read_dir(\"./queries\").unwrap();\n\n    // save output to `target/.../out/` dir\n    let out_dir = env::var_os(\"OUT_DIR\").unwrap();\n    let dest_dir = Path::new(&out_dir);\n\n    // iterate over files (this could be easier by using `glob` crate)\n    for path in paths {\n        // paths\n        let prql_path = path.unwrap().path();\n        let sql_path = dest_dir.join(prql_path.file_name().unwrap());\n\n        // read file\n        let prql_string = fs::read_to_string(prql_path).unwrap();\n\n        // compile\n        let sql_string = compile(&prql_string, &Options::default()).unwrap();\n\n        // write file\n        fs::write(sql_path, sql_string).unwrap();\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/examples/compile-files/queries/arrays.prql",
    "content": "let x = p1 -> s\"x({p1})\"\n\nfrom [{a=null}, {a=2}]\nfilter (a | in [2, 4])\nselect {\n  empty_array = [],\n  single_element = [42],\n  null_element = [null],\n  complex_expressions = [a + a, (a * 2) + 1],\n  nested_function_calls = [(min a), (max a ?? 0)],\n  passing_as_arg = x [1,2,3],\n  nested = ['a', ['b']]\n}\n"
  },
  {
    "path": "prqlc/prqlc/examples/compile-files/queries/query1.prql",
    "content": "from albums\nselect {title, artist_id}\njoin artists (==artist_id)\n"
  },
  {
    "path": "prqlc/prqlc/examples/compile-files/queries/variables.prql",
    "content": "from employees\nfilter country == \"USA\"                      # Each line transforms the previous result.\nderive {                                     # This adds columns / variables.\n  gross_salary = salary + payroll_tax,\n  gross_cost = gross_salary + benefits_cost  # Variables can use other variables.\n}\nfilter gross_cost > 0\ngroup {title, country} (                     # For each group use a nested pipeline\n  aggregate {                                # Aggregate each group to a single row\n    average salary,\n    average gross_salary,\n    sum salary,\n    sum gross_salary,\n    average gross_cost,\n    sum_gross_cost = sum gross_cost,\n    ct = count salary,\n  }\n)\nsort sum_gross_cost\nfilter ct > 200\ntake 20\n"
  },
  {
    "path": "prqlc/prqlc/examples/compile-files/src/main.rs",
    "content": "// a helper macro\n#[macro_export]\nmacro_rules! include_query {\n    ($filename:expr) => {\n        include_str!(concat!(env!(\"OUT_DIR\"), \"/\", $filename))\n    };\n}\n\nfn main() {\n    // queries are accessible under their original filename\n    let compiler_query: &str = include_query!(\"query1.prql\");\n\n    println!(\"{compiler_query}\");\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/docs_generator.rs",
    "content": "use prqlc::pr::{ExprKind, Stmt, StmtKind, TyKind, VarDefKind};\n\n/// Generate HTML documentation.\npub fn generate_html_docs(stmts: Vec<Stmt>) -> String {\n    let html = format!(\n        r#\"<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"keywords\" content=\"prql\">\n    <meta name=\"generator\" content=\"prqlc {}\">\n    <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN\" crossorigin=\"anonymous\">\n    <title>PRQL Docs</title>\n  </head>\n  <body>\n    <header class=\"bg-body-tertiary\">\n      <div class=\"container\">\n        <h1>Documentation</h1>\n      </div>\n    </header>\n    <main class=\"container\">\n      {{{{ content }}}}\n    </main>\n    <footer class=\"container border-top\">\n      <small class=\"text-body-secondary\">Generated with <a href=\"https://prql-lang.org/\" rel=\"external\" target=\"_blank\">prqlc</a> {}.</small>\n    </footer>\n  </body>\n</html>\n\"#,\n        prqlc::compiler_version(),\n        prqlc::compiler_version()\n    );\n\n    let mut docs = String::new();\n\n    docs.push_str(\"<h2>Functions</h2>\\n\");\n    docs.push_str(\"<ul>\\n\");\n    for stmt in stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::VarDef(_)))\n    {\n        let var_def = stmt.kind.as_var_def().unwrap();\n        docs.push_str(&format!(\n            \"  <li><a href=\\\"#fn-{}\\\">{}</a></li>\\n\",\n            var_def.name, var_def.name\n        ));\n    }\n    docs.push_str(\"</ul>\\n\\n\");\n    if stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::VarDef(_)))\n        .count()\n        == 0\n    {\n        docs.push_str(\"<p>None.</p>\\n\\n\");\n    }\n\n    if stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::TypeDef(_)))\n        .count()\n        > 0\n    {\n        docs.push_str(\"<h2>Types</h2>\\n\");\n        docs.push_str(\"<ul>\\n\");\n        for stmt in stmts\n            .clone()\n            .into_iter()\n            .filter(|stmt| matches!(stmt.kind, StmtKind::TypeDef(_)))\n        {\n            let type_def = stmt.kind.as_type_def().unwrap();\n            docs.push_str(&format!(\n                \"  <li><code>{}</code> – {:?}</li>\\n\",\n                type_def.name, type_def.value.kind\n            ));\n        }\n        docs.push_str(\"</ul>\\n\");\n    }\n\n    if stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::ModuleDef(_)))\n        .count()\n        > 0\n    {\n        docs.push_str(\"<h2>Modules</h2>\\n\");\n        docs.push_str(\"<ul>\\n\");\n        for stmt in stmts\n            .clone()\n            .into_iter()\n            .filter(|stmt| matches!(stmt.kind, StmtKind::ModuleDef(_)))\n        {\n            let module_def = stmt.kind.as_module_def().unwrap();\n            docs.push_str(&format!(\"  <li>{}</li>\\n\", module_def.name));\n        }\n        docs.push_str(\"</ul>\\n\");\n    }\n\n    for stmt in stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::VarDef(_)))\n    {\n        let var_def = stmt.kind.as_var_def().unwrap();\n        if var_def.kind != VarDefKind::Let {\n            continue;\n        }\n\n        docs.push_str(\"<section>\\n\");\n        docs.push_str(&format!(\n            \"  <h3 id=\\\"fn-{}\\\">{}</h3>\\n\",\n            var_def.name, var_def.name\n        ));\n\n        docs.push_str(\"<div class=\\\"ms-3\\\">\\n\");\n\n        if let Some(doc_comment) = stmt.doc_comment {\n            docs.push_str(&format!(\"  <p>{doc_comment}</p>\\n\"));\n        }\n\n        if let Some(expr) = &var_def.value {\n            match &expr.kind {\n                ExprKind::Func(func) => {\n                    if !func.params.is_empty() {\n                        docs.push_str(\"  <h4 class=\\\"h6\\\">Parameters</h4>\\n\");\n                        docs.push_str(\"  <ul>\\n\");\n                        for param in &func.params {\n                            docs.push_str(&format!(\"    <li><var>{}</var></li>\\n\", param.name));\n                        }\n                        docs.push_str(\"  </ul>\\n\");\n                    }\n\n                    if !func.named_params.is_empty() {\n                        docs.push_str(\"  <h4 class=\\\"h6\\\">Named parameters</h4>\\n\");\n                        docs.push_str(\"  <ul>\\n\");\n                        for param in &func.named_params {\n                            docs.push_str(&format!(\"    <li><var>{}</var></li>\\n\", param.name));\n                        }\n                        docs.push_str(\"  </ul>\\n\");\n                    }\n\n                    if let Some(return_ty) = &func.return_ty {\n                        docs.push_str(\"  <h4 class=\\\"h6\\\">Returns</h4>\\n\");\n                        match &return_ty.kind {\n                            TyKind::Ident(ident) => {\n                                docs.push_str(&format!(\"  <p><code>{}</code></p>\\n\", ident.name));\n                            }\n                            TyKind::Primitive(primitive) => {\n                                docs.push_str(&format!(\"  <p><code>{primitive}</code></p>\\n\"));\n                            }\n                            _ => docs.push_str(\"  <p class=\\\"text-danger\\\">Not implemented</p>\\n\"),\n                        }\n                    }\n                }\n                ExprKind::Pipeline(_) => {\n                    docs.push_str(\"  <p>There is a pipeline.</p>\\n\");\n                }\n                _ => (),\n            }\n        }\n\n        docs.push_str(\"</div>\\n\");\n        docs.push_str(\"</section>\\n\");\n    }\n\n    html.replacen(\"{{ content }}\", &docs, 1)\n}\n\n/// Generate Markdown documentation.\npub fn generate_markdown_docs(stmts: Vec<Stmt>) -> String {\n    let markdown = format!(\n        r#\"# Documentation\n\n{{{{ content }}}}\n\nGenerated with [prqlc](https://prql-lang.org/) {}.\n\"#,\n        prqlc::compiler_version()\n    );\n\n    let mut docs = String::new();\n\n    docs.push_str(\"## Functions\\n\");\n    for stmt in stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::VarDef(_)))\n    {\n        let var_def = stmt.kind.as_var_def().unwrap();\n        docs.push_str(&format!(\"* [{}](#{})\\n\", var_def.name, var_def.name));\n    }\n    docs.push('\\n');\n\n    if stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::VarDef(_)))\n        .count()\n        == 0\n    {\n        docs.push_str(\"None.\\n\\n\");\n    }\n\n    if stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::TypeDef(_)))\n        .count()\n        > 0\n    {\n        docs.push_str(\"## Types\\n\");\n        for stmt in stmts\n            .clone()\n            .into_iter()\n            .filter(|stmt| matches!(stmt.kind, StmtKind::TypeDef(_)))\n        {\n            let type_def = stmt.kind.as_type_def().unwrap();\n            docs.push_str(&format!(\n                \"* `{}` – {:?}\\n\",\n                type_def.name, type_def.value.kind\n            ));\n        }\n        docs.push('\\n');\n    }\n\n    if stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::ModuleDef(_)))\n        .count()\n        > 0\n    {\n        docs.push_str(\"## Modules\\n\");\n        for stmt in stmts\n            .clone()\n            .into_iter()\n            .filter(|stmt| matches!(stmt.kind, StmtKind::ModuleDef(_)))\n        {\n            let module_def = stmt.kind.as_module_def().unwrap();\n            docs.push_str(&format!(\"* {}\\n\", module_def.name));\n        }\n        docs.push('\\n');\n    }\n\n    for stmt in stmts\n        .clone()\n        .into_iter()\n        .filter(|stmt| matches!(stmt.kind, StmtKind::VarDef(_)))\n    {\n        let var_def = stmt.kind.as_var_def().unwrap();\n        if var_def.kind != VarDefKind::Let {\n            continue;\n        }\n\n        docs.push_str(&format!(\"### {}\\n\", var_def.name));\n\n        if let Some(doc_comment) = stmt.doc_comment {\n            docs.push_str(&format!(\"{}\\n\", doc_comment.trim_start()));\n        }\n        docs.push('\\n');\n\n        if let Some(expr) = &var_def.value {\n            match &expr.kind {\n                ExprKind::Func(func) => {\n                    if !func.params.is_empty() {\n                        docs.push_str(\"#### Parameters\\n\");\n                        for param in &func.params {\n                            docs.push_str(&format!(\"* *{}*\\n\", param.name));\n                        }\n                        docs.push('\\n');\n                    }\n\n                    if !func.named_params.is_empty() {\n                        docs.push_str(\"#### Named parameters\\n\");\n                        for param in &func.named_params {\n                            docs.push_str(&format!(\"* *{}*\\n\", param.name));\n                        }\n                        docs.push('\\n');\n                    }\n\n                    if let Some(return_ty) = &func.return_ty {\n                        docs.push_str(\"#### Returns\\n\");\n                        match &return_ty.kind {\n                            TyKind::Ident(ident) => {\n                                docs.push_str(&format!(\"`{}`\\n\", ident.name));\n                            }\n                            TyKind::Primitive(primitive) => {\n                                docs.push_str(&format!(\"`{primitive}`\\n\"));\n                            }\n                            _ => docs.push_str(\"Not implemented\\n\"),\n                        }\n                    }\n                    docs.push('\\n');\n                }\n                ExprKind::Pipeline(_) => {\n                    docs.push_str(\"There is a pipeline.\\n\");\n                }\n                _ => (),\n            }\n        }\n    }\n\n    markdown.replacen(\"{{ content }}\", &docs, 1)\n}\n\n#[cfg(test)]\nmod tests {\n    use std::process::Command;\n\n    use insta_cmd::assert_cmd_snapshot;\n    use insta_cmd::get_cargo_bin;\n\n    #[test]\n    fn generate_html_docs() {\n        std::env::set_var(\"PRQL_VERSION_OVERRIDE\", env!(\"CARGO_PKG_VERSION\"));\n\n        let input = r\"\n        #! This is the x function.\n        let x = arg1 arg2 -> c\n        let fn_returns_array = -> <array> array\n        let fn_returns_bool = -> <bool> true\n        let fn_returns_float = -> <float> float\n        let fn_returns_int = -> <int> 0\n        let fn_returns_text = -> <text> 'text'\n\n        module foo {}\n\n        type user_id = int\n        \";\n\n        assert_cmd_snapshot!(prqlc_command().args([\"experimental\", \"doc\", \"--format=html\"]).pass_stdin(input), @r##\"\n        success: true\n        exit_code: 0\n        ----- stdout -----\n        <!doctype html>\n        <html lang=\"en\">\n          <head>\n            <meta charset=\"utf-8\">\n            <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n            <meta name=\"keywords\" content=\"prql\">\n            <meta name=\"generator\" content=\"prqlc 0.13.12\">\n            <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN\" crossorigin=\"anonymous\">\n            <title>PRQL Docs</title>\n          </head>\n          <body>\n            <header class=\"bg-body-tertiary\">\n              <div class=\"container\">\n                <h1>Documentation</h1>\n              </div>\n            </header>\n            <main class=\"container\">\n              <h2>Functions</h2>\n        <ul>\n          <li><a href=\"#fn-x\">x</a></li>\n          <li><a href=\"#fn-fn_returns_array\">fn_returns_array</a></li>\n          <li><a href=\"#fn-fn_returns_bool\">fn_returns_bool</a></li>\n          <li><a href=\"#fn-fn_returns_float\">fn_returns_float</a></li>\n          <li><a href=\"#fn-fn_returns_int\">fn_returns_int</a></li>\n          <li><a href=\"#fn-fn_returns_text\">fn_returns_text</a></li>\n        </ul>\n\n        <h2>Types</h2>\n        <ul>\n          <li><code>user_id</code> – Primitive(Int)</li>\n        </ul>\n        <h2>Modules</h2>\n        <ul>\n          <li>foo</li>\n        </ul>\n        <section>\n          <h3 id=\"fn-x\">x</h3>\n        <div class=\"ms-3\">\n          <p> This is the x function.</p>\n          <h4 class=\"h6\">Parameters</h4>\n          <ul>\n            <li><var>arg1</var></li>\n            <li><var>arg2</var></li>\n          </ul>\n        </div>\n        </section>\n        <section>\n          <h3 id=\"fn-fn_returns_array\">fn_returns_array</h3>\n        <div class=\"ms-3\">\n          <h4 class=\"h6\">Returns</h4>\n          <p><code>array</code></p>\n        </div>\n        </section>\n        <section>\n          <h3 id=\"fn-fn_returns_bool\">fn_returns_bool</h3>\n        <div class=\"ms-3\">\n          <h4 class=\"h6\">Returns</h4>\n          <p><code>bool</code></p>\n        </div>\n        </section>\n        <section>\n          <h3 id=\"fn-fn_returns_float\">fn_returns_float</h3>\n        <div class=\"ms-3\">\n          <h4 class=\"h6\">Returns</h4>\n          <p><code>float</code></p>\n        </div>\n        </section>\n        <section>\n          <h3 id=\"fn-fn_returns_int\">fn_returns_int</h3>\n        <div class=\"ms-3\">\n          <h4 class=\"h6\">Returns</h4>\n          <p><code>int</code></p>\n        </div>\n        </section>\n        <section>\n          <h3 id=\"fn-fn_returns_text\">fn_returns_text</h3>\n        <div class=\"ms-3\">\n          <h4 class=\"h6\">Returns</h4>\n          <p><code>text</code></p>\n        </div>\n        </section>\n\n            </main>\n            <footer class=\"container border-top\">\n              <small class=\"text-body-secondary\">Generated with <a href=\"https://prql-lang.org/\" rel=\"external\" target=\"_blank\">prqlc</a> 0.13.12.</small>\n            </footer>\n          </body>\n        </html>\n\n        ----- stderr -----\n        \"##);\n    }\n\n    #[test]\n    fn generate_markdown_docs() {\n        std::env::set_var(\"PRQL_VERSION_OVERRIDE\", env!(\"CARGO_PKG_VERSION\"));\n\n        let input = r\"\n        #! This is the x function.\n        let x = arg1 arg2 -> c\n        let fn_returns_array = -> <array> array\n        let fn_returns_bool = -> <bool> true\n        let fn_returns_float = -> <float> float\n        let fn_returns_int = -> <int> 0\n        let fn_returns_text = -> <text> 'text'\n\n        module foo {}\n\n        type user_id = int\n        \";\n\n        assert_cmd_snapshot!(prqlc_command().args([\"experimental\", \"doc\"]).pass_stdin(input), @\"\n        success: true\n        exit_code: 0\n        ----- stdout -----\n        # Documentation\n\n        ## Functions\n        * [x](#x)\n        * [fn_returns_array](#fn_returns_array)\n        * [fn_returns_bool](#fn_returns_bool)\n        * [fn_returns_float](#fn_returns_float)\n        * [fn_returns_int](#fn_returns_int)\n        * [fn_returns_text](#fn_returns_text)\n\n        ## Types\n        * `user_id` – Primitive(Int)\n\n        ## Modules\n        * foo\n\n        ### x\n        This is the x function.\n\n        #### Parameters\n        * *arg1*\n        * *arg2*\n\n\n        ### fn_returns_array\n\n        #### Returns\n        `array`\n\n        ### fn_returns_bool\n\n        #### Returns\n        `bool`\n\n        ### fn_returns_float\n\n        #### Returns\n        `float`\n\n        ### fn_returns_int\n\n        #### Returns\n        `int`\n\n        ### fn_returns_text\n\n        #### Returns\n        `text`\n\n\n\n        Generated with [prqlc](https://prql-lang.org/) 0.13.12.\n\n        ----- stderr -----\n        \");\n    }\n\n    fn prqlc_command() -> Command {\n        let mut cmd = Command::new(get_cargo_bin(\"prqlc\"));\n        normalize_prqlc(&mut cmd);\n        cmd\n    }\n\n    fn normalize_prqlc(cmd: &mut Command) -> &mut Command {\n        cmd\n            // We set `CLICOLOR_FORCE` in CI to force color output, but we don't want `prqlc` to\n            // output color for our snapshot tests. And it seems to override the\n            // `--color=never` flag.\n            .env_remove(\"CLICOLOR_FORCE\")\n            .env(\"NO_COLOR\", \"1\")\n            .args([\"--color=never\"])\n            // We don't want the tests to be affected by the user's `RUST_BACKTRACE` setting.\n            .env_remove(\"RUST_BACKTRACE\")\n            .env_remove(\"RUST_LOG\")\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/highlight.rs",
    "content": "use color_eyre::owo_colors::OwoColorize;\nuse prqlc::{\n    lr::{TokenKind, Tokens},\n    pr::Literal,\n};\n\n/// Highlight PRQL code printed to the terminal.\npub(crate) fn highlight(tokens: &Tokens) -> String {\n    let mut output = String::new();\n    let mut last = 0;\n\n    for token in &tokens.0 {\n        let diff = token.span.start - last;\n        last = token.span.end;\n        output.push_str(&\" \".repeat(diff));\n        output.push_str(&highlight_token_kind(&token.kind));\n    }\n\n    output\n}\n\nfn highlight_token_kind(token: &TokenKind) -> String {\n    // LineWrap is recursive with TokenKind, so we needed to split this function\n    // out from the one above (otherwise would have it as a single func)\n    let mut output = String::new();\n    match &token {\n        TokenKind::NewLine => output.push('\\n'),\n        TokenKind::Ident(ident) => {\n            if is_transform(ident) {\n                output.push_str(&ident.green().to_string())\n            } else {\n                output.push_str(&ident.to_string())\n            }\n        }\n        TokenKind::Keyword(keyword) => output.push_str(&keyword.blue().to_string()),\n        TokenKind::Literal(literal) => output.push_str(&match literal {\n            Literal::Null => literal.green().bold().to_string(),\n            Literal::Integer(_) => literal.green().to_string(),\n            Literal::Float(_) => literal.green().to_string(),\n            Literal::Boolean(_) => literal.green().bold().to_string(),\n            Literal::String(_) => literal.yellow().to_string(),\n            _ => literal.to_string(),\n        }),\n        TokenKind::Param(param) => output.push_str(&param.purple().to_string()),\n        TokenKind::Range {\n            bind_left: _,\n            bind_right: _,\n        } => output.push_str(\"..\"),\n        TokenKind::Interpolation(_, _) => output.push_str(&format!(\"{}\", token.yellow())),\n        TokenKind::Control(char) => output.push(*char),\n        TokenKind::ArrowThin\n        | TokenKind::ArrowFat\n        | TokenKind::Eq\n        | TokenKind::Ne\n        | TokenKind::Gte\n        | TokenKind::Lte\n        | TokenKind::RegexSearch => output.push_str(&format!(\"{token}\")),\n        TokenKind::And | TokenKind::Or => output.push_str(&format!(\"{token}\").purple().to_string()),\n        TokenKind::Coalesce | TokenKind::DivInt | TokenKind::Pow | TokenKind::Annotate => {\n            output.push_str(&format!(\"{token}\"))\n        }\n        TokenKind::Comment(comment) => output.push_str(\n            &format!(\"#{comment}\")\n                .truecolor(95, 135, 135)\n                .italic()\n                .to_string(),\n        ),\n        TokenKind::DocComment(comment) => output.push_str(\n            &format!(\"#!{comment}\")\n                .truecolor(95, 135, 135)\n                .italic()\n                .to_string(),\n        ),\n        TokenKind::LineWrap(inner_tokens) => {\n            output.push_str(\"\\n\\\\\");\n            for t in inner_tokens {\n                output.push_str(&highlight_token_kind(t));\n            }\n        }\n        TokenKind::Start => {}\n    }\n    output\n}\n\nfn is_transform(ident: &str) -> bool {\n    // TODO: Could we instead source these from the standard library?\n    // We could also use the semantic understanding from later compiler stages?\n    matches!(\n        ident,\n        \"from\"\n            | \"derive\"\n            | \"select\"\n            | \"filter\"\n            | \"sort\"\n            | \"join\"\n            | \"take\"\n            | \"group\"\n            | \"aggregate\"\n            | \"window\"\n            | \"loop\"\n    )\n}\n\n#[cfg(test)]\nmod tests {\n    use std::process::Command;\n\n    use insta_cmd::assert_cmd_snapshot;\n    use insta_cmd::get_cargo_bin;\n\n    #[test]\n    fn highlight() {\n        // (Colors don't show because they're disabled; we could have a test\n        // that forces them to show?)\n        assert_cmd_snapshot!(prqlc_command().args([\"experimental\", \"highlight\"]).pass_stdin(r#\"\n        from tracks\n        filter artist == \"Bob Marley\"                 # Each line transforms the previous result\n        aggregate {                                   # `aggregate` reduces each column to a value\n          plays    = sum plays,\n          longest  = max length,\n          shortest = min length,                      # Trailing commas are allowed\n        }\n\n        \"#), @r#\"\n        success: true\n        exit_code: 0\n        ----- stdout -----\n\n                from tracks\n                filter artist == \"Bob Marley\"                 # Each line transforms the previous result\n                aggregate {                                   # `aggregate` reduces each column to a value\n                  plays    = sum plays,\n                  longest  = max length,\n                  shortest = min length,                      # Trailing commas are allowed\n                }\n\n\n        ----- stderr -----\n        \"#);\n    }\n\n    // TODO: import from existing location, need to adjust visibility\n    fn prqlc_command() -> Command {\n        let mut cmd = Command::new(get_cargo_bin(\"prqlc\"));\n        normalize_prqlc(&mut cmd);\n        cmd\n    }\n\n    fn normalize_prqlc(cmd: &mut Command) -> &mut Command {\n        cmd\n            // We set `CLICOLOR_FORCE` in CI to force color output, but we don't want `prqlc` to\n            // output color for our snapshot tests. And it seems to override the\n            // `--color=never` flag.\n            .env_remove(\"CLICOLOR_FORCE\")\n            .env(\"NO_COLOR\", \"1\")\n            .args([\"--color=never\"])\n            // We don't want the tests to be affected by the user's `RUST_BACKTRACE` setting.\n            .env_remove(\"RUST_BACKTRACE\")\n            .env_remove(\"RUST_LOG\")\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/jinja.rs",
    "content": "//! Handling of Jinja templates\n//!\n//! dbt is using the following pipeline: `Jinja+SQL -> SQL -> execution`.\n//!\n//! To prevent messing up the templates, we have create the following pipeline:\n//! ```\n//! Jinja+PRQL -> Jinja+SQL -> SQL -> execution\n//! ```\n//!\n//! But because prqlc does not (and should not) know how to handle Jinja,\n//! we have to extract the interpolations, replace them something that is valid PRQL,\n//! compile the query and inject interpolations back in.\n//!\n//! Unfortunately, this requires parsing Jinja.\n//!\n//! use `crate::compiler::tokens::{Span`, Token};\n\nuse std::collections::HashMap;\n\nuse anyhow::Result;\nuse minijinja::{\n    machinery::{Span, Token, WhitespaceConfig},\n    syntax::SyntaxConfig,\n};\nuse regex::Regex;\n\nconst ANCHOR_PREFIX: &str = \"_jinja_\";\n\n#[derive(Debug)]\npub enum JinjaBlock<'a> {\n    Data(&'a str),\n    Interpolation(Vec<(Token<'a>, Span)>),\n}\n\n#[derive(Default)]\npub struct JinjaContext<'a> {\n    anchor_map: HashMap<String, &'a str>,\n    header: Vec<&'a str>,\n}\n\n/// Parse source as Jinja template, extract all interpolations\n/// and replace them with anchors.\npub fn pre_process(source: &str) -> Result<(String, JinjaContext<'_>)> {\n    let mut blocks = Vec::new();\n    let mut current_block = Vec::new();\n\n    for res in\n        minijinja::machinery::tokenize(source, false, SyntaxConfig, WhitespaceConfig::default())\n    {\n        let (token, span) = res?;\n\n        if let Token::TemplateData(data) = token {\n            if !current_block.is_empty() {\n                blocks.push(JinjaBlock::Interpolation(current_block));\n                current_block = Vec::new();\n            }\n            blocks.push(JinjaBlock::Data(data))\n        } else {\n            current_block.push((token, span));\n        }\n    }\n    if !current_block.is_empty() {\n        blocks.push(JinjaBlock::Interpolation(current_block));\n    }\n\n    let mut anchored_source = String::new();\n    let mut next_anchor_id = 0;\n    let mut context = JinjaContext::default();\n    for block in blocks {\n        match block {\n            JinjaBlock::Data(data) => anchored_source += data,\n            JinjaBlock::Interpolation(block) => {\n                let (tokens, spans): (Vec<_>, _) = block.into_iter().unzip();\n\n                let source_span = find_span(source, spans);\n\n                if let Some(Token::Ident(\"config\" | \"set\")) = tokens.get(1) {\n                    context.header.push(source_span);\n                } else {\n                    let id = format!(\"{ANCHOR_PREFIX}{next_anchor_id}\");\n                    next_anchor_id += 1;\n\n                    anchored_source += &id;\n\n                    context.anchor_map.insert(id, source_span);\n                }\n            }\n        }\n    }\n\n    Ok((anchored_source, context))\n}\n\nfn find_span(source: &str, spans: Vec<Span>) -> &str {\n    let start = spans.first().unwrap();\n    let end = spans.last().unwrap();\n\n    let mut start_index = 0;\n    let mut end_index = source.len();\n\n    let mut line = 1;\n    let mut col = 0;\n    for (index, char) in source.chars().enumerate() {\n        if char == '\\n' {\n            line += 1;\n            col = 0;\n            continue;\n        } else {\n            col += 1;\n        }\n\n        if line == start.start_line && col == start.start_col {\n            start_index = index;\n        }\n        if line == end.end_line && col == end.end_col {\n            end_index = index + 1;\n        }\n    }\n    &source[start_index..end_index]\n}\n\n/// Replace anchors with their values.\npub fn post_process(source: &str, context: JinjaContext) -> String {\n    let mut res = String::new();\n\n    for stmt in context.header {\n        res += stmt;\n        res += \"\\n\";\n    }\n\n    let re = Regex::new(&format!(r\"{ANCHOR_PREFIX}\\d+\")).unwrap();\n\n    let mut last_index = 0;\n    for cap in re.captures_iter(source) {\n        let cap = cap.get(0).unwrap();\n        let index = cap.start();\n        let anchor_id = cap.as_str();\n\n        res += &source[last_index..index];\n        res += context.anchor_map.get(anchor_id).unwrap_or(&anchor_id);\n\n        last_index = index + anchor_id.len();\n    }\n    res += &source[last_index..];\n\n    res\n}\n\n#[cfg(test)]\nmod test {\n    use super::{post_process, pre_process, Span};\n\n    #[test]\n    fn test_find_span() {\n        let text = r#\"line 1 col 13\n        line 2 col 21\n        some text line 3 col 31 more text\n        \"#;\n\n        assert_eq!(\n            super::find_span(\n                text,\n                vec![\n                    Span {\n                        start_line: 2,\n                        start_col: 9,\n                        start_offset: 0,\n                        end_line: 1234,\n                        end_col: 5678,\n                        end_offset: 0\n                    },\n                    Span {\n                        start_line: 9999,\n                        start_col: 65535,\n                        start_offset: 0,\n                        end_line: 3,\n                        end_col: 31,\n                        end_offset: 0\n                    }\n                ]\n            ),\n            r#\"line 2 col 21\n        some text line 3 col 31\"#\n        );\n    }\n\n    #[test]\n    fn test_pre_process() {\n        let src = r###\"from in_process = {{ source('salesforce', 'in_process') }}\"###;\n        let (pre_proc_text, ctx) = pre_process(src).unwrap();\n        insta::assert_yaml_snapshot!(pre_proc_text, @\"from in_process = _jinja_0\");\n        insta::assert_yaml_snapshot!(ctx.anchor_map[\"_jinja_0\"], @r#\"\" {{ source('salesforce', 'in_process') }}\"\"#);\n    }\n\n    #[test]\n    fn test_post_process() {\n        let src = r###\"from in_process = {{ source('salesforce', 'in_process') }}\"###;\n        let (pre_proc_text, ctx) = pre_process(src).unwrap();\n        let post_proc_text = post_process(&pre_proc_text, ctx);\n        insta::assert_yaml_snapshot!(post_proc_text, @r#\"\"from in_process =  {{ source('salesforce', 'in_process') }}\"\"#);\n    }\n\n    #[test]\n    fn test_config_interpolation() {\n        let src = r###\"{{ config(materialized = \"table\") }}\\nfrom in_process = {{ source('salesforce', 'in_process') }}\"###;\n        let (pre_proc_text, ctx) = pre_process(src).unwrap();\n        insta::assert_yaml_snapshot!(ctx.header, @r#\"- \"{{ config(materialized = \\\"table\\\") }}\"\"#);\n        let post_proc_text = post_process(&pre_proc_text, ctx);\n        insta::assert_yaml_snapshot!(post_proc_text, @r#\"\"{{ config(materialized = \\\"table\\\") }}\\n\\\\nfrom in_process =  {{ source('salesforce', 'in_process') }}\"\"#);\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/lsp.rs",
    "content": "use std::error::Error;\n\nuse lsp_types::OneOf;\nuse lsp_types::{\n    request::GotoDefinition, GotoDefinitionResponse, InitializeParams, ServerCapabilities,\n};\n\nuse lsp_server::{Connection, ExtractError, Message, Request, RequestId, Response};\n\npub fn run() -> Result<(), Box<dyn Error + Sync + Send>> {\n    // Note that  we must have our logging only write out to stderr.\n    eprintln!(\"starting PRQL LSP server\");\n\n    // Create the transport. Includes the stdio (stdin and stdout) versions but this could\n    // also be implemented to use sockets or HTTP.\n    let (connection, io_threads) = Connection::stdio();\n\n    // Run the server and wait for the two threads to end (typically by trigger LSP Exit event).\n    let server_capabilities = serde_json::to_value(&ServerCapabilities {\n        definition_provider: Some(OneOf::Left(true)),\n        ..Default::default()\n    })\n    .unwrap();\n    let initialization_params = match connection.initialize(server_capabilities) {\n        Ok(it) => it,\n        Err(e) => {\n            if e.channel_is_disconnected() {\n                io_threads.join()?;\n            }\n            return Err(e.into());\n        }\n    };\n    main_loop(connection, initialization_params)?;\n    io_threads.join()?;\n\n    // Shut down gracefully.\n    eprintln!(\"shutting down server\");\n    Ok(())\n}\n\nfn main_loop(\n    connection: Connection,\n    params: serde_json::Value,\n) -> Result<(), Box<dyn Error + Sync + Send>> {\n    let _params: InitializeParams = serde_json::from_value(params).unwrap();\n    eprintln!(\"starting main loop\");\n    for msg in &connection.receiver {\n        eprintln!(\"got msg: {msg:?}\");\n        match msg {\n            Message::Request(req) => {\n                if connection.handle_shutdown(&req)? {\n                    return Ok(());\n                }\n                eprintln!(\"got request: {req:?}\");\n                match cast::<GotoDefinition>(req) {\n                    Ok((id, params)) => {\n                        eprintln!(\"got gotoDefinition request #{id}: {params:?}\");\n                        let result = Some(GotoDefinitionResponse::Array(Vec::new()));\n                        let result = serde_json::to_value(&result).unwrap();\n                        let resp = Response {\n                            id,\n                            result: Some(result),\n                            error: None,\n                        };\n                        connection.sender.send(Message::Response(resp))?;\n                        continue;\n                    }\n                    Err(err @ ExtractError::JsonError { .. }) => panic!(\"{err:?}\"),\n                    Err(ExtractError::MethodMismatch(req)) => req,\n                };\n                // ...\n            }\n            Message::Response(resp) => {\n                eprintln!(\"got response: {resp:?}\");\n            }\n            Message::Notification(not) => {\n                eprintln!(\"got notification: {not:?}\");\n                return Ok(());\n            }\n        }\n    }\n    Ok(())\n}\n\nfn cast<R>(req: Request) -> Result<(RequestId, R::Params), ExtractError<Request>>\nwhere\n    R: lsp_types::request::Request,\n    R::Params: serde::de::DeserializeOwned,\n{\n    req.extract(R::METHOD)\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/mod.rs",
    "content": "#![cfg(not(target_family = \"wasm\"))]\n\nuse std::collections::HashMap;\nuse std::env;\nuse std::fs::File;\nuse std::io::{self, BufWriter, Read, Write};\nuse std::ops::Range;\nuse std::path::{Path, PathBuf};\nuse std::process::exit;\nuse std::str::FromStr;\n\nuse anstream::{eprintln, println};\nuse anyhow::anyhow;\nuse anyhow::bail;\nuse anyhow::Result;\nuse ariadne::Source;\nuse clap::{CommandFactory, Parser, Subcommand, ValueHint};\nuse clio::has_extension;\nuse clio::Output;\nuse is_terminal::IsTerminal;\nuse itertools::Itertools;\nuse schemars::schema_for;\n\nuse prqlc::compiler_version;\nuse prqlc::debug;\nuse prqlc::internal::pl_to_lineage;\nuse prqlc::ir::{pl, rq};\nuse prqlc::pr;\nuse prqlc::semantic;\nuse prqlc::semantic::reporting::FrameCollector;\nuse prqlc::utils::maybe_strip_colors;\nuse prqlc::{pl_to_prql, pl_to_rq_tree, prql_to_pl, prql_to_pl_tree, prql_to_tokens, rq_to_sql};\nuse prqlc::{Options, SourceTree, Target};\n\nmod docs_generator;\nmod highlight;\nmod jinja;\n#[cfg(feature = \"lsp\")]\nmod lsp;\n#[cfg(test)]\nmod test;\nmod watch;\n\n/// Entrypoint called by [`crate::main`]\npub fn main() -> color_eyre::eyre::Result<()> {\n    let cli = Cli::parse();\n\n    // redirect all log messages into the [debug::DebugLog]\n    if has_debug_log(&cli) {\n        static LOGGER: debug::MessageLogger = debug::MessageLogger;\n        log::set_logger(&LOGGER)\n            .map(|()| log::set_max_level(log::LevelFilter::max()))\n            .unwrap();\n    }\n\n    color_eyre::install()?;\n    cli.color.write_global();\n\n    if let Some(mut subcommand) = cli.command {\n        if let Err(error) = subcommand.run() {\n            eprintln!(\"{error}\");\n            // Copied from\n            // https://doc.rust-lang.org/src/std/backtrace.rs.html#1-504, since it's private\n            fn backtrace_enabled() -> bool {\n                match env::var(\"RUST_LIB_BACKTRACE\") {\n                    Ok(s) => s != \"0\",\n                    Err(_) => match env::var(\"RUST_BACKTRACE\") {\n                        Ok(s) => s != \"0\",\n                        Err(_) => false,\n                    },\n                }\n            }\n            if backtrace_enabled() {\n                eprintln!(\"{:#}\", error.backtrace());\n            }\n\n            exit(1)\n        }\n    } else {\n        Cli::command().print_help()?;\n    }\n\n    Ok(())\n}\n\n#[derive(Parser, Debug, Clone)]\nstruct Cli {\n    #[command(subcommand)]\n    command: Option<Command>,\n    #[command(flatten)]\n    color: colorchoice_clap::Color,\n}\n\n/// This seems to be required because passing `compiler_version()` directly to\n/// `command` fails because it's not a string, and we can't seem to convert it\n/// to a string inline.\npub fn compiler_version_str() -> &'static str {\n    static COMPILER_VERSION: std::sync::OnceLock<String> = std::sync::OnceLock::new();\n    COMPILER_VERSION.get_or_init(|| compiler_version().to_string())\n}\n\n#[derive(Subcommand, Debug, Clone)]\n#[command(name = env!(\"CARGO_PKG_NAME\"), about, version=compiler_version_str())]\nenum Command {\n    /// Parse into PL AST\n    Parse {\n        #[command(flatten)]\n        io_args: IoArgs,\n        #[arg(value_enum, long, default_value = \"yaml\")]\n        format: Format,\n    },\n\n    /// Lex into Lexer Representation\n    Lex {\n        #[command(flatten)]\n        io_args: IoArgs,\n        #[arg(value_enum, long, default_value = \"yaml\")]\n        format: Format,\n    },\n\n    /// Parse & generate PRQL code back\n    #[command(name = \"fmt\")]\n    Format {\n        #[arg(value_parser, default_value = \"-\", value_hint(ValueHint::AnyPath))]\n        input: clio::ClioPath,\n    },\n\n    /// Parse the whole project and collect it into a single PRQL source file\n    #[command(name = \"collect\")]\n    Collect(IoArgs),\n\n    #[command(subcommand)]\n    Debug(DebugCommand),\n\n    #[command(subcommand)]\n    Experimental(ExperimentalCommand),\n\n    /// Parse, resolve, lower into RQ & compile to SQL\n    ///\n    /// Only displays the main pipeline and does not handle loop.\n    #[command(name = \"compile\")]\n    Compile {\n        #[command(flatten)]\n        io_args: IoArgs,\n\n        /// Exclude the signature comment containing the PRQL version\n        #[arg(long = \"hide-signature-comment\", action = clap::ArgAction::SetFalse)]\n        signature_comment: bool,\n\n        /// Emit unformatted, dense SQL\n        #[arg(long = \"no-format\", action = clap::ArgAction::SetFalse)]\n        format: bool,\n\n        /// Target to compile to\n        #[arg(short, long, default_value = \"sql.any\", env = \"PRQLC_TARGET\")]\n        target: String,\n\n        /// File path into which to write the debug log to.\n        #[arg(long, env = \"PRQLC_DEBUG_LOG\")]\n        debug_log: Option<PathBuf>,\n    },\n\n    /// Watch a directory and compile .prql files to .sql files\n    Watch(watch::WatchArgs),\n\n    /// Show available compile target names\n    #[command(name = \"list-targets\")]\n    ListTargets,\n\n    /// Language Server Protocol\n    #[command(hide = true)]\n    Lsp,\n\n    /// Print a shell completion for supported shells\n    #[command(name = \"shell-completion\")]\n    ShellCompletion {\n        #[arg(value_enum)]\n        shell: clap_complete_command::Shell,\n    },\n}\n\n/// Commands for meant for debugging, prone to change\n#[derive(Subcommand, Debug, Clone)]\nenum DebugCommand {\n    /// Parse, resolve & combine source with comments annotating relation type\n    Annotate(IoArgs),\n\n    /// Output column-level lineage graph\n    ///\n    /// The returned data includes:\n    ///\n    /// * \"frames\": a list of Span and Lineage records corresponding to each\n    ///   transformation frame in the main pipeline.\n    ///\n    /// * \"nodes\": a list of expression graph nodes.\n    ///\n    /// * \"ast\": the parsed PL abstract syntax tree.\n    ///\n    /// Each expression node has attributes:\n    ///\n    /// * \"id\": A unique ID for each expression.\n    ///\n    /// * \"kind\": Descriptive text about the expression type.\n    ///\n    /// * \"span\": Position of the expression in the original source (optional).\n    ///\n    /// * \"alias\": When this expression is part of a Tuple, this is its alias\n    ///   (optional).\n    ///\n    /// * \"ident\": When this expression is an Ident, this is its reference\n    ///   (optional).\n    ///\n    /// * \"targets\": Any upstream sources of data for this expression, as a list\n    ///   of node IDs (optional).\n    ///\n    /// * \"children\": A list of expression IDs contained within this expression\n    ///   (optional).\n    ///\n    /// * \"parent\": The expression ID that contains this expression (optional).\n    ///\n    /// A Python script for rendering this output as a GraphViz visualization is\n    /// available at https://gist.github.com/kgutwin/efe5f03df5ff930d899249018a0a551b.\n    Lineage {\n        #[command(flatten)]\n        io_args: IoArgs,\n        #[arg(value_enum, long, default_value = \"yaml\")]\n        format: Format,\n    },\n\n    /// Print info about the AST data structure\n    Ast,\n\n    /// Print JSON Schema\n    JsonSchema {\n        #[arg(value_enum, long)]\n        ir_type: IntermediateRepr,\n    },\n}\n\n/// Experimental commands are prone to change\n#[derive(Subcommand, Debug, Clone)]\nenum ExperimentalCommand {\n    /// Generate Markdown documentation\n    #[command(name = \"doc\")]\n    GenerateDocs {\n        #[command(flatten)]\n        io_args: IoArgs,\n        #[arg(value_enum, long, default_value = \"markdown\")]\n        format: DocsFormat,\n    },\n\n    /// Syntax highlight\n    #[command(name = \"highlight\")]\n    Highlight(IoArgs),\n}\n\n#[derive(clap::Args, Default, Debug, Clone)]\npub struct IoArgs {\n    #[arg(value_parser, default_value = \"-\", value_hint(ValueHint::AnyPath))]\n    input: clio::ClioPath,\n\n    #[arg(value_parser, default_value = \"-\", value_hint(ValueHint::FilePath))]\n    output: Output,\n\n    /// Identifier of the main pipeline.\n    #[arg(value_parser, value_hint(ValueHint::Unknown))]\n    main_path: Option<String>,\n}\n\n#[derive(clap::ValueEnum, Clone, Debug)]\nenum DocsFormat {\n    Html,\n    Markdown,\n}\n\n#[derive(clap::ValueEnum, Clone, Debug)]\nenum Format {\n    Json,\n    Yaml,\n}\n\n#[derive(clap::ValueEnum, Clone, Debug)]\nenum IntermediateRepr {\n    Pl,\n    Rq,\n    Lineage,\n}\n\nimpl Command {\n    /// Entrypoint called by [`main`]\n    pub fn run(&mut self) -> Result<()> {\n        match self {\n            Command::Watch(command) => watch::run(command),\n            Command::ListTargets => self.list_targets(),\n            // Format is handled differently to the other IO commands, since it\n            // always writes to the same output.\n            Command::Format { input } => {\n                let sources = read_files(input)?;\n                let root = sources.root;\n\n                for (path, source) in sources.sources {\n                    let ast = prql_to_pl(&source)?;\n\n                    // If we're writing to stdout (though could this be nicer?\n                    // We're discarding many of the benefits of Clio here...)\n                    if path.as_os_str() == \"\" {\n                        let mut output: Output = Output::new(input.path())?;\n                        output.write_all(&pl_to_prql(&ast)?.into_bytes())?;\n                        break;\n                    }\n\n                    let path_buf = root\n                        .as_ref()\n                        .map_or_else(|| path.clone(), |root| root.join(&path));\n                    let path_str = path_buf.to_str().ok_or_else(|| {\n                        anyhow!(\"Path `{}` is not valid UTF-8\", path_buf.display())\n                    })?;\n                    let mut output: Output = Output::new(path_str)?;\n\n                    output.write_all(&pl_to_prql(&ast)?.into_bytes())?;\n                }\n                Ok(())\n            }\n            Command::ShellCompletion { shell } => {\n                shell.generate(&mut Cli::command(), &mut std::io::stdout());\n                Ok(())\n            }\n            Command::Debug(DebugCommand::Ast) => {\n                prqlc::ir::pl::print_mem_sizes();\n                Ok(())\n            }\n            Command::Debug(DebugCommand::JsonSchema { ir_type }) => {\n                let schema = match ir_type {\n                    IntermediateRepr::Pl => schema_for!(pl::ModuleDef),\n                    IntermediateRepr::Rq => schema_for!(rq::RelationalQuery),\n                    IntermediateRepr::Lineage => schema_for!(FrameCollector),\n                };\n                io::stdout().write_all(&serde_json::to_string_pretty(&schema)?.into_bytes())?;\n                Ok(())\n            }\n            #[cfg(feature = \"lsp\")]\n            Command::Lsp => match lsp::run() {\n                Ok(_) => Ok(()),\n                Err(err) => Err(anyhow!(err)),\n            },\n            _ => self.run_io_command(),\n        }\n    }\n\n    fn list_targets(&self) -> std::result::Result<(), anyhow::Error> {\n        println!(\"{}\", Target::names().join(\"\\n\"));\n        Ok(())\n    }\n\n    fn run_io_command(&mut self) -> std::result::Result<(), anyhow::Error> {\n        let (mut file_tree, main_path) = self.read_input()?;\n\n        self.execute(&mut file_tree, &main_path)\n            .and_then(|buf| Ok(self.write_output(&buf)?))\n    }\n\n    fn execute<'a>(&self, sources: &'a mut SourceTree, main_path: &'a str) -> Result<Vec<u8>> {\n        let main_path = main_path\n            .split('.')\n            .filter(|x| !x.is_empty())\n            .map(str::to_string)\n            .collect_vec();\n\n        Ok(match self {\n            Command::Parse { format, .. } => {\n                let ast = prql_to_pl_tree(sources)?;\n                match format {\n                    Format::Json => serde_json::to_string_pretty(&ast)?.into_bytes(),\n                    Format::Yaml => serde_yaml::to_string(&ast)?.into_bytes(),\n                }\n            }\n            Command::Lex { format, .. } => {\n                let s = sources.sources.values().exactly_one().or_else(|_| {\n                    // TODO: allow multiple sources\n                    bail!(\"Currently `lex` only works with a single source, but found multiple sources\")\n                })?;\n                let tokens = prql_to_tokens(s)?;\n                match format {\n                    Format::Json => serde_json::to_string_pretty(&tokens)?.into_bytes(),\n                    Format::Yaml => serde_yaml::to_string(&tokens)?.into_bytes(),\n                }\n            }\n            Command::Collect(_) => {\n                let mut root_module_def = prql_to_pl_tree(sources)?;\n\n                drop_module_def(&mut root_module_def.stmts, \"std\");\n\n                pl_to_prql(&root_module_def)?.into_bytes()\n            }\n            Command::Debug(DebugCommand::Annotate(_)) => {\n                let (_, source) = sources.sources.clone().into_iter().exactly_one().or_else(\n                    |_| bail!(\n                        \"Currently `annotate` only works with a single source, but found multiple sources: {:?}\",\n                        sources.sources.keys()\n                            .map(|x| x.display().to_string())\n                            .sorted()\n                            .map(|x| format!(\"`{x}`\"))\n                            .join(\", \")\n                    )\n                )?;\n\n                // TODO: potentially if there is code performing a role beyond\n                // presentation, it should be a library function; and we could\n                // promote it to the `prqlc` crate.\n                let root_mod = prql_to_pl(&source)?;\n\n                // resolve\n                let ctx = semantic::resolve(root_mod)?;\n\n                let frames = if let Ok((main, _)) = ctx.find_main_rel(&[]) {\n                    semantic::reporting::collect_frames(*main.clone().into_relation_var().unwrap())\n                        .frames\n                } else {\n                    vec![]\n                };\n\n                // combine with source\n                combine_prql_and_frames(&source, frames).as_bytes().to_vec()\n            }\n            Command::Debug(DebugCommand::Lineage { format, .. }) => {\n                let stmts = prql_to_pl_tree(sources)?;\n                let fc = pl_to_lineage(stmts)?;\n\n                match format {\n                    Format::Json => serde_json::to_string_pretty(&fc)?.into_bytes(),\n                    Format::Yaml => serde_yaml::to_string(&fc)?.into_bytes(),\n                }\n            }\n            Command::Experimental(ExperimentalCommand::GenerateDocs { format, .. }) => {\n                let module_ref = prql_to_pl_tree(sources)?;\n\n                match format {\n                    DocsFormat::Html => {\n                        docs_generator::generate_html_docs(module_ref.stmts).into_bytes()\n                    }\n                    DocsFormat::Markdown => {\n                        docs_generator::generate_markdown_docs(module_ref.stmts).into_bytes()\n                    }\n                }\n            }\n            Command::Experimental(ExperimentalCommand::Highlight(_)) => {\n                let s = sources.sources.values().exactly_one().or_else(|_| {\n                    // TODO: allow multiple sources\n                    bail!(\"Currently `highlight` only works with a single source, but found multiple sources\")\n                })?;\n                let tokens = prql_to_tokens(s)?;\n\n                maybe_strip_colors(&highlight::highlight(&tokens)).into_bytes()\n            }\n            Command::Compile {\n                signature_comment,\n                format,\n                target,\n                debug_log,\n                ..\n            } => {\n                if debug_log.is_some() {\n                    debug::log_start();\n                }\n\n                let opts = Options::default()\n                    .with_target(Target::from_str(target).map_err(prqlc::ErrorMessages::from)?)\n                    .with_signature_comment(*signature_comment)\n                    .with_format(*format);\n\n                let res = prql_to_pl_tree(sources)\n                    .and_then(|pl| {\n                        pl_to_rq_tree(pl, &main_path, &[semantic::NS_DEFAULT_DB.to_string()])\n                    })\n                    .and_then(|rq| rq_to_sql(rq, &opts))\n                    .map_err(|e| e.composed(sources));\n\n                if let Some(path) = debug_log {\n                    write_log(path)?;\n                }\n\n                res?.as_bytes().to_vec()\n            }\n            _ => unreachable!(\"Other commands shouldn't reach `execute`\"),\n        })\n    }\n\n    fn read_input(&mut self) -> Result<(SourceTree, String)> {\n        // Possibly this should be called by the relevant subcommands passing in\n        // `input`, rather than matching on them and grabbing `input` from\n        // `self`? But possibly if everything moves to `io_args`, then this is\n        // quite reasonable?\n        use Command::*;\n        let io_args = match self {\n            Parse { io_args, .. }\n            | Lex { io_args, .. }\n            | Collect(io_args)\n            | Compile { io_args, .. }\n            | Debug(DebugCommand::Annotate(io_args) | DebugCommand::Lineage { io_args, .. }) => {\n                io_args\n            }\n            Experimental(ExperimentalCommand::GenerateDocs { io_args, .. }) => io_args,\n            Experimental(ExperimentalCommand::Highlight(io_args)) => io_args,\n            _ => unreachable!(),\n        };\n        let input = &mut io_args.input;\n\n        // Don't wait without a prompt when running `prqlc compile` —\n        // it's confusing whether it's waiting for input or not. This\n        // offers the prompt.\n        //\n        // See https://github.com/PRQL/prql/issues/3228 for details on us not\n        // yet using `input.is_tty()`.\n        if input.path() == Path::new(\"-\") && std::io::stdin().is_terminal() {\n            #[cfg(unix)]\n            eprintln!(\"Enter PRQL, then press ctrl-d to compile:\\n\");\n            #[cfg(windows)]\n            eprintln!(\"Enter PRQL, then press ctrl-z to compile:\\n\");\n        }\n\n        let sources = read_files(input)?;\n\n        let main_path = io_args.main_path.clone().unwrap_or_default();\n\n        Ok((sources, main_path))\n    }\n\n    fn write_output(&mut self, data: &[u8]) -> std::io::Result<()> {\n        use Command::{Collect, Compile, Debug, Experimental, Lex, Parse};\n        let mut output = match self {\n            Parse { io_args, .. }\n            | Lex { io_args, .. }\n            | Collect(io_args)\n            | Compile { io_args, .. }\n            | Debug(DebugCommand::Annotate(io_args) | DebugCommand::Lineage { io_args, .. }) => {\n                io_args.output.clone()\n            }\n            Experimental(ExperimentalCommand::GenerateDocs { io_args, .. }) => {\n                io_args.output.clone()\n            }\n            Experimental(ExperimentalCommand::Highlight(io_args)) => io_args.output.clone(),\n            _ => unreachable!(),\n        };\n        output.write_all(data)\n    }\n}\n\nfn has_debug_log(cli: &Cli) -> bool {\n    matches!(\n        cli.command,\n        Some(Command::Compile {\n            debug_log: Some(_),\n            ..\n        })\n    )\n}\n\npub fn write_log(path: &std::path::Path) -> Result<()> {\n    let debug_log = if let Some(debug_log) = debug::log_finish() {\n        debug_log\n    } else {\n        return Err(anyhow!(\n            \"debug log was started, but it cannot be found after compilation\"\n        ));\n    };\n    match path.extension().and_then(|s| s.to_str()) {\n        Some(\"json\") => {\n            let file = BufWriter::new(File::create(path)?);\n            serde_json::to_writer(file, &debug_log)?;\n        }\n        Some(\"html\") => {\n            let file = BufWriter::new(File::create(path)?);\n            debug::render_log_to_html(file, &debug_log)?;\n        }\n        _ => {\n            return Err(anyhow!(\"unknown debug log format for file {path:?}\"));\n        }\n    }\n    Ok(())\n}\n\nfn drop_module_def(stmts: &mut Vec<pr::Stmt>, name: &str) {\n    stmts.retain(|x| x.kind.as_module_def().map_or(true, |m| m.name != name));\n}\n\nfn read_files(input: &mut clio::ClioPath) -> Result<SourceTree> {\n    // Should this function move to a SourceTree constructor?\n    let root = input.path();\n\n    let mut sources = HashMap::new();\n    for file in input.clone().files(has_extension(\"prql\"))? {\n        let path = file.path().strip_prefix(root)?.to_owned();\n\n        let mut file_contents = String::new();\n        file.open()?.read_to_string(&mut file_contents)?;\n\n        sources.insert(path, file_contents);\n    }\n    Ok(SourceTree::new(sources, Some(root.to_path_buf())))\n}\n\nfn combine_prql_and_frames(source: &str, frames: Vec<(Option<pr::Span>, pl::Lineage)>) -> String {\n    let source = Source::from(source);\n    let lines = source.lines().collect_vec();\n    let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);\n\n    // Not sure this is the nicest construction. Some of this was added in the\n    // upgrade from ariande 0.3.0 to 0.4.0 and possibly there are more elegant\n    // ways to build the output. (Though `.get_line_text` seems to be the only\n    // method to get actual text out of a `Source`...)\n    let mut printed_lines_count = 0;\n    let mut result = Vec::new();\n    for (span, frame) in frames {\n        if let Some(span) = span {\n            let line_len = source.get_line_range(&Range::from(span)).end - 1;\n\n            while printed_lines_count < line_len {\n                result.push(\n                    source\n                        .get_line_text(source.line(printed_lines_count).unwrap())\n                        .unwrap()\n                        // Ariadne 0.4.1 added a line break at the end of the line, so we\n                        // trim it.\n                        .trim_end()\n                        .to_string(),\n                );\n                printed_lines_count += 1;\n            }\n\n            if printed_lines_count >= lines.len() {\n                break;\n            }\n            let chars: String = source\n                .get_line_text(source.line(printed_lines_count).unwrap())\n                .unwrap()\n                // Ariadne 0.4.1 added a line break at the end of the line, so we\n                // trim it.\n                .trim_end()\n                .to_string();\n            printed_lines_count += 1;\n\n            result.push(format!(\"{chars:width$} # {frame}\"));\n        }\n    }\n    for line in lines.iter().skip(printed_lines_count) {\n        result.push(source.get_line_text(line.to_owned()).unwrap().to_string());\n    }\n\n    result.into_iter().join(\"\\n\") + \"\\n\"\n}\n\n/// Unit tests for `prqlc`. Integration tests (where we call the actual binary)\n/// are in `prqlc/tests/test.rs`.\n#[cfg(test)]\nmod tests {\n    use insta::assert_snapshot;\n\n    use super::*;\n\n    #[test]\n    fn layouts() {\n        let output = Command::execute(\n            &Command::Debug(DebugCommand::Annotate(IoArgs::default())),\n            &mut r#\"\nfrom initial_table\nselect {f = first_name, l = last_name, gender}\nderive full_name = f\"{f} {l}\"\ntake 23\nselect {f\"{l} {f}\", full = full_name, gender}\nsort full\n        \"#\n            .into(),\n            \"\",\n        )\n        .unwrap();\n        assert_snapshot!(String::from_utf8(output).unwrap().trim(),\n        @r#\"\n        from initial_table\n        select {f = first_name, l = last_name, gender}  # [f, l, initial_table.gender]\n        derive full_name = f\"{f} {l}\"                   # [f, l, initial_table.gender, full_name]\n        take 23                                         # [f, l, initial_table.gender, full_name]\n        select {f\"{l} {f}\", full = full_name, gender}   # [?, full, initial_table.gender]\n        sort full                                       # [?, full, initial_table.gender]\n        \"#);\n    }\n\n    /// Check we get an error on a bad input\n    #[test]\n    fn compile_bad() {\n        anstream::ColorChoice::Never.write_global();\n\n        let result = Command::execute(\n            &Command::Compile {\n                io_args: IoArgs::default(),\n                signature_comment: false,\n                format: true,\n                target: \"sql.any\".to_string(),\n                debug_log: None,\n            },\n            &mut \"asdf\".into(),\n            \"\",\n        );\n\n        assert_snapshot!(&result.unwrap_err().to_string(), @r\"\n        Error:\n           ╭─[ :1:1 ]\n           │\n         1 │ asdf\n           │ ──┬─\n           │   ╰─── Unknown name `asdf`\n        ───╯\n        \");\n    }\n\n    #[test]\n    fn compile() {\n        let result = Command::execute(\n            &Command::Compile {\n                io_args: IoArgs::default(),\n                signature_comment: false,\n                format: true,\n                target: \"sql.any\".to_string(),\n                debug_log: None,\n            },\n            &mut SourceTree::new(\n                [\n                    (\"Project.prql\".into(), \"orders.x | select y\".to_string()),\n                    (\n                        \"orders.prql\".into(),\n                        \"let x = (from z | select {y, u})\".to_string(),\n                    ),\n                ],\n                None,\n            ),\n            \"main\",\n        )\n        .unwrap();\n        assert_snapshot!(String::from_utf8(result).unwrap().trim(), @r\"\n        WITH x AS (\n          SELECT\n            y,\n            u\n          FROM\n            z\n        )\n        SELECT\n          y\n        FROM\n          x\n        \");\n    }\n\n    #[test]\n    fn parse() {\n        let output = Command::execute(\n            &Command::Parse {\n                io_args: IoArgs::default(),\n                format: Format::Yaml,\n            },\n            &mut \"from x | select y\".into(),\n            \"\",\n        )\n        .unwrap();\n\n        assert_snapshot!(String::from_utf8(output).unwrap().trim(), @r\"\n        name: Project\n        stmts:\n        - VarDef:\n            kind: Main\n            name: main\n            value:\n              Pipeline:\n                exprs:\n                - FuncCall:\n                    name:\n                      Ident:\n                      - from\n                      span: 1:0-4\n                    args:\n                    - Ident:\n                      - x\n                      span: 1:5-6\n                  span: 1:0-6\n                - FuncCall:\n                    name:\n                      Ident:\n                      - select\n                      span: 1:9-15\n                    args:\n                    - Ident:\n                      - y\n                      span: 1:16-17\n                  span: 1:9-17\n              span: 1:0-17\n          span: 1:0-17\n        \");\n    }\n    #[test]\n    fn lex() {\n        let output = Command::execute(\n            &Command::Lex {\n                io_args: IoArgs::default(),\n                format: Format::Yaml,\n            },\n            &mut \"from x | select y\".into(),\n            \"\",\n        )\n        .unwrap();\n\n        // TODO: terser output; maybe serialize span as `0..4`? Remove the\n        // `!Ident` complication?\n        assert_snapshot!(String::from_utf8(output).unwrap().trim(), @r\"\n        - kind: Start\n          span:\n            start: 0\n            end: 0\n        - kind: !Ident from\n          span:\n            start: 0\n            end: 4\n        - kind: !Ident x\n          span:\n            start: 5\n            end: 6\n        - kind: !Control '|'\n          span:\n            start: 7\n            end: 8\n        - kind: !Ident select\n          span:\n            start: 9\n            end: 15\n        - kind: !Ident y\n          span:\n            start: 16\n            end: 17\n        \");\n    }\n    #[test]\n    fn lex_nested_enum() {\n        let output = Command::execute(\n            &Command::Lex {\n                io_args: IoArgs::default(),\n                format: Format::Yaml,\n            },\n            &mut r#\"\n            from tracks\n            take 10\n            \"#\n            .into(),\n            \"\",\n        )\n        .unwrap();\n\n        assert_snapshot!(String::from_utf8(output).unwrap().trim(), @r\"\n        - kind: Start\n          span:\n            start: 0\n            end: 0\n        - kind: NewLine\n          span:\n            start: 0\n            end: 1\n        - kind: !Ident from\n          span:\n            start: 13\n            end: 17\n        - kind: !Ident tracks\n          span:\n            start: 18\n            end: 24\n        - kind: NewLine\n          span:\n            start: 24\n            end: 25\n        - kind: !Ident take\n          span:\n            start: 37\n            end: 41\n        - kind: !Literal\n            Integer: 10\n          span:\n            start: 42\n            end: 44\n        - kind: NewLine\n          span:\n            start: 44\n            end: 45\n        \");\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion-2.snap",
    "content": "---\nsource: prqlc/prqlc/src/cli/test.rs\ninfo:\n  program: prqlc\n  args:\n    - \"--color=never\"\n    - shell-completion\n    - fish\n  env:\n    CLICOLOR_FORCE: \"\"\n    NO_COLOR: \"1\"\n    RUST_BACKTRACE: \"\"\n    RUST_LOG: \"\"\n---\nsuccess: true\nexit_code: 0\n----- stdout -----\n# Print an optspec for argparse to handle cmd's options that are independent of any subcommand.\nfunction __fish_prqlc_global_optspecs\n\tstring join \\n color= h/help V/version\nend\n\nfunction __fish_prqlc_needs_command\n\t# Figure out if the current invocation already has a command.\n\tset -l cmd (commandline -opc)\n\tset -e cmd[1]\n\targparse -s (__fish_prqlc_global_optspecs) -- $cmd 2>/dev/null\n\tor return\n\tif set -q argv[1]\n\t\t# Also print the command, so this can be used to figure out what it is.\n\t\techo $argv[1]\n\t\treturn 1\n\tend\n\treturn 0\nend\n\nfunction __fish_prqlc_using_subcommand\n\tset -l cmd (__fish_prqlc_needs_command)\n\ttest -z \"$cmd\"\n\tand return 1\n\tcontains -- $cmd[1] $argv\nend\n\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -s V -l version -d 'Print version'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"parse\" -d 'Parse into PL AST'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"lex\" -d 'Lex into Lexer Representation'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"fmt\" -d 'Parse & generate PRQL code back'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"collect\" -d 'Parse the whole project and collect it into a single PRQL source file'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"debug\" -d 'Commands for meant for debugging, prone to change'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"experimental\" -d 'Experimental commands are prone to change'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"compile\" -d 'Parse, resolve, lower into RQ & compile to SQL'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"watch\" -d 'Watch a directory and compile .prql files to .sql files'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"list-targets\" -d 'Show available compile target names'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"lsp\" -d 'Language Server Protocol'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"shell-completion\" -d 'Print a shell completion for supported shells'\ncomplete -c prqlc -n \"__fish_prqlc_needs_command\" -f -a \"help\" -d 'Print this message or the help of the given subcommand(s)'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand parse\" -l format -r -f -a \"json\\t''\nyaml\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand parse\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand parse\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand lex\" -l format -r -f -a \"json\\t''\nyaml\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand lex\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand lex\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand fmt\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand fmt\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand collect\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand collect\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and not __fish_seen_subcommand_from annotate lineage ast json-schema help\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and not __fish_seen_subcommand_from annotate lineage ast json-schema help\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and not __fish_seen_subcommand_from annotate lineage ast json-schema help\" -f -a \"annotate\" -d 'Parse, resolve & combine source with comments annotating relation type'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and not __fish_seen_subcommand_from annotate lineage ast json-schema help\" -f -a \"lineage\" -d 'Output column-level lineage graph'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and not __fish_seen_subcommand_from annotate lineage ast json-schema help\" -f -a \"ast\" -d 'Print info about the AST data structure'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and not __fish_seen_subcommand_from annotate lineage ast json-schema help\" -f -a \"json-schema\" -d 'Print JSON Schema'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and not __fish_seen_subcommand_from annotate lineage ast json-schema help\" -f -a \"help\" -d 'Print this message or the help of the given subcommand(s)'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from annotate\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from annotate\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from lineage\" -l format -r -f -a \"json\\t''\nyaml\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from lineage\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from lineage\" -s h -l help -d 'Print help (see more with \\'--help\\')'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from ast\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from ast\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from json-schema\" -l ir-type -r -f -a \"pl\\t''\nrq\\t''\nlineage\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from json-schema\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from json-schema\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from help\" -f -a \"annotate\" -d 'Parse, resolve & combine source with comments annotating relation type'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from help\" -f -a \"lineage\" -d 'Output column-level lineage graph'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from help\" -f -a \"ast\" -d 'Print info about the AST data structure'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from help\" -f -a \"json-schema\" -d 'Print JSON Schema'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand debug; and __fish_seen_subcommand_from help\" -f -a \"help\" -d 'Print this message or the help of the given subcommand(s)'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and not __fish_seen_subcommand_from doc highlight help\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and not __fish_seen_subcommand_from doc highlight help\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and not __fish_seen_subcommand_from doc highlight help\" -f -a \"doc\" -d 'Generate Markdown documentation'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and not __fish_seen_subcommand_from doc highlight help\" -f -a \"highlight\" -d 'Syntax highlight'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and not __fish_seen_subcommand_from doc highlight help\" -f -a \"help\" -d 'Print this message or the help of the given subcommand(s)'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and __fish_seen_subcommand_from doc\" -l format -r -f -a \"html\\t''\nmarkdown\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and __fish_seen_subcommand_from doc\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and __fish_seen_subcommand_from doc\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and __fish_seen_subcommand_from highlight\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and __fish_seen_subcommand_from highlight\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and __fish_seen_subcommand_from help\" -f -a \"doc\" -d 'Generate Markdown documentation'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and __fish_seen_subcommand_from help\" -f -a \"highlight\" -d 'Syntax highlight'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand experimental; and __fish_seen_subcommand_from help\" -f -a \"help\" -d 'Print this message or the help of the given subcommand(s)'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand compile\" -s t -l target -d 'Target to compile to' -r\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand compile\" -l debug-log -d 'File path into which to write the debug log to' -r -F\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand compile\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand compile\" -l hide-signature-comment -d 'Exclude the signature comment containing the PRQL version'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand compile\" -l no-format -d 'Emit unformatted, dense SQL'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand compile\" -s h -l help -d 'Print help (see more with \\'--help\\')'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand watch\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand watch\" -l no-format\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand watch\" -l no-signature\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand watch\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand list-targets\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand list-targets\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand lsp\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand lsp\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand shell-completion\" -l color -d 'Controls when to use color' -r -f -a \"auto\\t''\nalways\\t''\nnever\\t''\"\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand shell-completion\" -s h -l help -d 'Print help'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"parse\" -d 'Parse into PL AST'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"lex\" -d 'Lex into Lexer Representation'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"fmt\" -d 'Parse & generate PRQL code back'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"collect\" -d 'Parse the whole project and collect it into a single PRQL source file'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"debug\" -d 'Commands for meant for debugging, prone to change'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"experimental\" -d 'Experimental commands are prone to change'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"compile\" -d 'Parse, resolve, lower into RQ & compile to SQL'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"watch\" -d 'Watch a directory and compile .prql files to .sql files'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"list-targets\" -d 'Show available compile target names'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"lsp\" -d 'Language Server Protocol'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"shell-completion\" -d 'Print a shell completion for supported shells'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and not __fish_seen_subcommand_from parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\" -f -a \"help\" -d 'Print this message or the help of the given subcommand(s)'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and __fish_seen_subcommand_from debug\" -f -a \"annotate\" -d 'Parse, resolve & combine source with comments annotating relation type'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and __fish_seen_subcommand_from debug\" -f -a \"lineage\" -d 'Output column-level lineage graph'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and __fish_seen_subcommand_from debug\" -f -a \"ast\" -d 'Print info about the AST data structure'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and __fish_seen_subcommand_from debug\" -f -a \"json-schema\" -d 'Print JSON Schema'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and __fish_seen_subcommand_from experimental\" -f -a \"doc\" -d 'Generate Markdown documentation'\ncomplete -c prqlc -n \"__fish_prqlc_using_subcommand help; and __fish_seen_subcommand_from experimental\" -f -a \"highlight\" -d 'Syntax highlight'\n\n----- stderr -----\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion-3.snap",
    "content": "---\nsource: prqlc/prqlc/src/cli/test.rs\ninfo:\n  program: prqlc\n  args:\n    - \"--color=never\"\n    - shell-completion\n    - powershell\n  env:\n    CLICOLOR_FORCE: \"\"\n    NO_COLOR: \"1\"\n    RUST_BACKTRACE: \"\"\n    RUST_LOG: \"\"\n---\nsuccess: true\nexit_code: 0\n----- stdout -----\n\nusing namespace System.Management.Automation\nusing namespace System.Management.Automation.Language\n\nRegister-ArgumentCompleter -Native -CommandName 'prqlc' -ScriptBlock {\n    param($wordToComplete, $commandAst, $cursorPosition)\n\n    $commandElements = $commandAst.CommandElements\n    $command = @(\n        'prqlc'\n        for ($i = 1; $i -lt $commandElements.Count; $i++) {\n            $element = $commandElements[$i]\n            if ($element -isnot [StringConstantExpressionAst] -or\n                $element.StringConstantType -ne [StringConstantType]::BareWord -or\n                $element.Value.StartsWith('-') -or\n                $element.Value -eq $wordToComplete) {\n                break\n        }\n        $element.Value\n    }) -join ';'\n\n    $completions = @(switch ($command) {\n        'prqlc' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')\n            [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')\n            [CompletionResult]::new('parse', 'parse', [CompletionResultType]::ParameterValue, 'Parse into PL AST')\n            [CompletionResult]::new('lex', 'lex', [CompletionResultType]::ParameterValue, 'Lex into Lexer Representation')\n            [CompletionResult]::new('fmt', 'fmt', [CompletionResultType]::ParameterValue, 'Parse & generate PRQL code back')\n            [CompletionResult]::new('collect', 'collect', [CompletionResultType]::ParameterValue, 'Parse the whole project and collect it into a single PRQL source file')\n            [CompletionResult]::new('debug', 'debug', [CompletionResultType]::ParameterValue, 'Commands for meant for debugging, prone to change')\n            [CompletionResult]::new('experimental', 'experimental', [CompletionResultType]::ParameterValue, 'Experimental commands are prone to change')\n            [CompletionResult]::new('compile', 'compile', [CompletionResultType]::ParameterValue, 'Parse, resolve, lower into RQ & compile to SQL')\n            [CompletionResult]::new('watch', 'watch', [CompletionResultType]::ParameterValue, 'Watch a directory and compile .prql files to .sql files')\n            [CompletionResult]::new('list-targets', 'list-targets', [CompletionResultType]::ParameterValue, 'Show available compile target names')\n            [CompletionResult]::new('lsp', 'lsp', [CompletionResultType]::ParameterValue, 'Language Server Protocol')\n            [CompletionResult]::new('shell-completion', 'shell-completion', [CompletionResultType]::ParameterValue, 'Print a shell completion for supported shells')\n            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')\n            break\n        }\n        'prqlc;parse' {\n            [CompletionResult]::new('--format', '--format', [CompletionResultType]::ParameterName, 'format')\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;lex' {\n            [CompletionResult]::new('--format', '--format', [CompletionResultType]::ParameterName, 'format')\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;fmt' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;collect' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;debug' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('annotate', 'annotate', [CompletionResultType]::ParameterValue, 'Parse, resolve & combine source with comments annotating relation type')\n            [CompletionResult]::new('lineage', 'lineage', [CompletionResultType]::ParameterValue, 'Output column-level lineage graph')\n            [CompletionResult]::new('ast', 'ast', [CompletionResultType]::ParameterValue, 'Print info about the AST data structure')\n            [CompletionResult]::new('json-schema', 'json-schema', [CompletionResultType]::ParameterValue, 'Print JSON Schema')\n            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')\n            break\n        }\n        'prqlc;debug;annotate' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;debug;lineage' {\n            [CompletionResult]::new('--format', '--format', [CompletionResultType]::ParameterName, 'format')\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')\n            break\n        }\n        'prqlc;debug;ast' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;debug;json-schema' {\n            [CompletionResult]::new('--ir-type', '--ir-type', [CompletionResultType]::ParameterName, 'ir-type')\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;debug;help' {\n            [CompletionResult]::new('annotate', 'annotate', [CompletionResultType]::ParameterValue, 'Parse, resolve & combine source with comments annotating relation type')\n            [CompletionResult]::new('lineage', 'lineage', [CompletionResultType]::ParameterValue, 'Output column-level lineage graph')\n            [CompletionResult]::new('ast', 'ast', [CompletionResultType]::ParameterValue, 'Print info about the AST data structure')\n            [CompletionResult]::new('json-schema', 'json-schema', [CompletionResultType]::ParameterValue, 'Print JSON Schema')\n            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')\n            break\n        }\n        'prqlc;debug;help;annotate' {\n            break\n        }\n        'prqlc;debug;help;lineage' {\n            break\n        }\n        'prqlc;debug;help;ast' {\n            break\n        }\n        'prqlc;debug;help;json-schema' {\n            break\n        }\n        'prqlc;debug;help;help' {\n            break\n        }\n        'prqlc;experimental' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('doc', 'doc', [CompletionResultType]::ParameterValue, 'Generate Markdown documentation')\n            [CompletionResult]::new('highlight', 'highlight', [CompletionResultType]::ParameterValue, 'Syntax highlight')\n            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')\n            break\n        }\n        'prqlc;experimental;doc' {\n            [CompletionResult]::new('--format', '--format', [CompletionResultType]::ParameterName, 'format')\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;experimental;highlight' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;experimental;help' {\n            [CompletionResult]::new('doc', 'doc', [CompletionResultType]::ParameterValue, 'Generate Markdown documentation')\n            [CompletionResult]::new('highlight', 'highlight', [CompletionResultType]::ParameterValue, 'Syntax highlight')\n            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')\n            break\n        }\n        'prqlc;experimental;help;doc' {\n            break\n        }\n        'prqlc;experimental;help;highlight' {\n            break\n        }\n        'prqlc;experimental;help;help' {\n            break\n        }\n        'prqlc;compile' {\n            [CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'Target to compile to')\n            [CompletionResult]::new('--target', '--target', [CompletionResultType]::ParameterName, 'Target to compile to')\n            [CompletionResult]::new('--debug-log', '--debug-log', [CompletionResultType]::ParameterName, 'File path into which to write the debug log to')\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('--hide-signature-comment', '--hide-signature-comment', [CompletionResultType]::ParameterName, 'Exclude the signature comment containing the PRQL version')\n            [CompletionResult]::new('--no-format', '--no-format', [CompletionResultType]::ParameterName, 'Emit unformatted, dense SQL')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')\n            break\n        }\n        'prqlc;watch' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('--no-format', '--no-format', [CompletionResultType]::ParameterName, 'no-format')\n            [CompletionResult]::new('--no-signature', '--no-signature', [CompletionResultType]::ParameterName, 'no-signature')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;list-targets' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;lsp' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;shell-completion' {\n            [CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Controls when to use color')\n            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')\n            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')\n            break\n        }\n        'prqlc;help' {\n            [CompletionResult]::new('parse', 'parse', [CompletionResultType]::ParameterValue, 'Parse into PL AST')\n            [CompletionResult]::new('lex', 'lex', [CompletionResultType]::ParameterValue, 'Lex into Lexer Representation')\n            [CompletionResult]::new('fmt', 'fmt', [CompletionResultType]::ParameterValue, 'Parse & generate PRQL code back')\n            [CompletionResult]::new('collect', 'collect', [CompletionResultType]::ParameterValue, 'Parse the whole project and collect it into a single PRQL source file')\n            [CompletionResult]::new('debug', 'debug', [CompletionResultType]::ParameterValue, 'Commands for meant for debugging, prone to change')\n            [CompletionResult]::new('experimental', 'experimental', [CompletionResultType]::ParameterValue, 'Experimental commands are prone to change')\n            [CompletionResult]::new('compile', 'compile', [CompletionResultType]::ParameterValue, 'Parse, resolve, lower into RQ & compile to SQL')\n            [CompletionResult]::new('watch', 'watch', [CompletionResultType]::ParameterValue, 'Watch a directory and compile .prql files to .sql files')\n            [CompletionResult]::new('list-targets', 'list-targets', [CompletionResultType]::ParameterValue, 'Show available compile target names')\n            [CompletionResult]::new('lsp', 'lsp', [CompletionResultType]::ParameterValue, 'Language Server Protocol')\n            [CompletionResult]::new('shell-completion', 'shell-completion', [CompletionResultType]::ParameterValue, 'Print a shell completion for supported shells')\n            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')\n            break\n        }\n        'prqlc;help;parse' {\n            break\n        }\n        'prqlc;help;lex' {\n            break\n        }\n        'prqlc;help;fmt' {\n            break\n        }\n        'prqlc;help;collect' {\n            break\n        }\n        'prqlc;help;debug' {\n            [CompletionResult]::new('annotate', 'annotate', [CompletionResultType]::ParameterValue, 'Parse, resolve & combine source with comments annotating relation type')\n            [CompletionResult]::new('lineage', 'lineage', [CompletionResultType]::ParameterValue, 'Output column-level lineage graph')\n            [CompletionResult]::new('ast', 'ast', [CompletionResultType]::ParameterValue, 'Print info about the AST data structure')\n            [CompletionResult]::new('json-schema', 'json-schema', [CompletionResultType]::ParameterValue, 'Print JSON Schema')\n            break\n        }\n        'prqlc;help;debug;annotate' {\n            break\n        }\n        'prqlc;help;debug;lineage' {\n            break\n        }\n        'prqlc;help;debug;ast' {\n            break\n        }\n        'prqlc;help;debug;json-schema' {\n            break\n        }\n        'prqlc;help;experimental' {\n            [CompletionResult]::new('doc', 'doc', [CompletionResultType]::ParameterValue, 'Generate Markdown documentation')\n            [CompletionResult]::new('highlight', 'highlight', [CompletionResultType]::ParameterValue, 'Syntax highlight')\n            break\n        }\n        'prqlc;help;experimental;doc' {\n            break\n        }\n        'prqlc;help;experimental;highlight' {\n            break\n        }\n        'prqlc;help;compile' {\n            break\n        }\n        'prqlc;help;watch' {\n            break\n        }\n        'prqlc;help;list-targets' {\n            break\n        }\n        'prqlc;help;lsp' {\n            break\n        }\n        'prqlc;help;shell-completion' {\n            break\n        }\n        'prqlc;help;help' {\n            break\n        }\n    })\n\n    $completions.Where{ $_.CompletionText -like \"$wordToComplete*\" } |\n        Sort-Object -Property ListItemText\n}\n\n----- stderr -----\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion-4.snap",
    "content": "---\nsource: prqlc/prqlc/src/cli/test.rs\ninfo:\n  program: prqlc\n  args:\n    - \"--color=never\"\n    - shell-completion\n    - zsh\n  env:\n    CLICOLOR_FORCE: \"\"\n    NO_COLOR: \"1\"\n    RUST_BACKTRACE: \"\"\n    RUST_LOG: \"\"\n---\nsuccess: true\nexit_code: 0\n----- stdout -----\n#compdef prqlc\n\nautoload -U is-at-least\n\n_prqlc() {\n    typeset -A opt_args\n    typeset -a _arguments_options\n    local ret=1\n\n    if is-at-least 5.2; then\n        _arguments_options=(-s -S -C)\n    else\n        _arguments_options=(-s -C)\n    fi\n\n    local context curcontext=\"$curcontext\" state line\n    _arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n'-V[Print version]' \\\n'--version[Print version]' \\\n\":: :_prqlc_commands\" \\\n\"*::: :->prqlc\" \\\n&& ret=0\n    case $state in\n    (prqlc)\n        words=($line[1] \"${words[@]}\")\n        (( CURRENT += 1 ))\n        curcontext=\"${curcontext%:*:*}:prqlc-command-$line[1]:\"\n        case $line[1] in\n            (parse)\n_arguments \"${_arguments_options[@]}\" : \\\n'--format=[]:FORMAT:(json yaml)' \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n'::input:_files' \\\n'::output:_files' \\\n'::main_path -- Identifier of the main pipeline:_default' \\\n&& ret=0\n;;\n(lex)\n_arguments \"${_arguments_options[@]}\" : \\\n'--format=[]:FORMAT:(json yaml)' \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n'::input:_files' \\\n'::output:_files' \\\n'::main_path -- Identifier of the main pipeline:_default' \\\n&& ret=0\n;;\n(fmt)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n'::input:_files' \\\n&& ret=0\n;;\n(collect)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n'::input:_files' \\\n'::output:_files' \\\n'::main_path -- Identifier of the main pipeline:_default' \\\n&& ret=0\n;;\n(debug)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n\":: :_prqlc__debug_commands\" \\\n\"*::: :->debug\" \\\n&& ret=0\n\n    case $state in\n    (debug)\n        words=($line[1] \"${words[@]}\")\n        (( CURRENT += 1 ))\n        curcontext=\"${curcontext%:*:*}:prqlc-debug-command-$line[1]:\"\n        case $line[1] in\n            (annotate)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n'::input:_files' \\\n'::output:_files' \\\n'::main_path -- Identifier of the main pipeline:_default' \\\n&& ret=0\n;;\n(lineage)\n_arguments \"${_arguments_options[@]}\" : \\\n'--format=[]:FORMAT:(json yaml)' \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help (see more with '\\''--help'\\'')]' \\\n'--help[Print help (see more with '\\''--help'\\'')]' \\\n'::input:_files' \\\n'::output:_files' \\\n'::main_path -- Identifier of the main pipeline:_default' \\\n&& ret=0\n;;\n(ast)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n&& ret=0\n;;\n(json-schema)\n_arguments \"${_arguments_options[@]}\" : \\\n'--ir-type=[]:IR_TYPE:(pl rq lineage)' \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n&& ret=0\n;;\n(help)\n_arguments \"${_arguments_options[@]}\" : \\\n\":: :_prqlc__debug__help_commands\" \\\n\"*::: :->help\" \\\n&& ret=0\n\n    case $state in\n    (help)\n        words=($line[1] \"${words[@]}\")\n        (( CURRENT += 1 ))\n        curcontext=\"${curcontext%:*:*}:prqlc-debug-help-command-$line[1]:\"\n        case $line[1] in\n            (annotate)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(lineage)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(ast)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(json-schema)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(help)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n        esac\n    ;;\nesac\n;;\n        esac\n    ;;\nesac\n;;\n(experimental)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n\":: :_prqlc__experimental_commands\" \\\n\"*::: :->experimental\" \\\n&& ret=0\n\n    case $state in\n    (experimental)\n        words=($line[1] \"${words[@]}\")\n        (( CURRENT += 1 ))\n        curcontext=\"${curcontext%:*:*}:prqlc-experimental-command-$line[1]:\"\n        case $line[1] in\n            (doc)\n_arguments \"${_arguments_options[@]}\" : \\\n'--format=[]:FORMAT:(html markdown)' \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n'::input:_files' \\\n'::output:_files' \\\n'::main_path -- Identifier of the main pipeline:_default' \\\n&& ret=0\n;;\n(highlight)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n'::input:_files' \\\n'::output:_files' \\\n'::main_path -- Identifier of the main pipeline:_default' \\\n&& ret=0\n;;\n(help)\n_arguments \"${_arguments_options[@]}\" : \\\n\":: :_prqlc__experimental__help_commands\" \\\n\"*::: :->help\" \\\n&& ret=0\n\n    case $state in\n    (help)\n        words=($line[1] \"${words[@]}\")\n        (( CURRENT += 1 ))\n        curcontext=\"${curcontext%:*:*}:prqlc-experimental-help-command-$line[1]:\"\n        case $line[1] in\n            (doc)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(highlight)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(help)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n        esac\n    ;;\nesac\n;;\n        esac\n    ;;\nesac\n;;\n(compile)\n_arguments \"${_arguments_options[@]}\" : \\\n'-t+[Target to compile to]:TARGET:_default' \\\n'--target=[Target to compile to]:TARGET:_default' \\\n'--debug-log=[File path into which to write the debug log to]:DEBUG_LOG:_files' \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'--hide-signature-comment[Exclude the signature comment containing the PRQL version]' \\\n'--no-format[Emit unformatted, dense SQL]' \\\n'-h[Print help (see more with '\\''--help'\\'')]' \\\n'--help[Print help (see more with '\\''--help'\\'')]' \\\n'::input:_files' \\\n'::output:_files' \\\n'::main_path -- Identifier of the main pipeline:_default' \\\n&& ret=0\n;;\n(watch)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'--no-format[]' \\\n'--no-signature[]' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n':path -- Directory or file to watch for changes:_default' \\\n&& ret=0\n;;\n(list-targets)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n&& ret=0\n;;\n(lsp)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n&& ret=0\n;;\n(shell-completion)\n_arguments \"${_arguments_options[@]}\" : \\\n'--color=[Controls when to use color]:WHEN:(auto always never)' \\\n'-h[Print help]' \\\n'--help[Print help]' \\\n':shell:(bash elvish fig fish nushell powershell zsh)' \\\n&& ret=0\n;;\n(help)\n_arguments \"${_arguments_options[@]}\" : \\\n\":: :_prqlc__help_commands\" \\\n\"*::: :->help\" \\\n&& ret=0\n\n    case $state in\n    (help)\n        words=($line[1] \"${words[@]}\")\n        (( CURRENT += 1 ))\n        curcontext=\"${curcontext%:*:*}:prqlc-help-command-$line[1]:\"\n        case $line[1] in\n            (parse)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(lex)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(fmt)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(collect)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(debug)\n_arguments \"${_arguments_options[@]}\" : \\\n\":: :_prqlc__help__debug_commands\" \\\n\"*::: :->debug\" \\\n&& ret=0\n\n    case $state in\n    (debug)\n        words=($line[1] \"${words[@]}\")\n        (( CURRENT += 1 ))\n        curcontext=\"${curcontext%:*:*}:prqlc-help-debug-command-$line[1]:\"\n        case $line[1] in\n            (annotate)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(lineage)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(ast)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(json-schema)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n        esac\n    ;;\nesac\n;;\n(experimental)\n_arguments \"${_arguments_options[@]}\" : \\\n\":: :_prqlc__help__experimental_commands\" \\\n\"*::: :->experimental\" \\\n&& ret=0\n\n    case $state in\n    (experimental)\n        words=($line[1] \"${words[@]}\")\n        (( CURRENT += 1 ))\n        curcontext=\"${curcontext%:*:*}:prqlc-help-experimental-command-$line[1]:\"\n        case $line[1] in\n            (doc)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(highlight)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n        esac\n    ;;\nesac\n;;\n(compile)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(watch)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(list-targets)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(lsp)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(shell-completion)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n(help)\n_arguments \"${_arguments_options[@]}\" : \\\n&& ret=0\n;;\n        esac\n    ;;\nesac\n;;\n        esac\n    ;;\nesac\n}\n\n(( $+functions[_prqlc_commands] )) ||\n_prqlc_commands() {\n    local commands; commands=(\n'parse:Parse into PL AST' \\\n'lex:Lex into Lexer Representation' \\\n'fmt:Parse & generate PRQL code back' \\\n'collect:Parse the whole project and collect it into a single PRQL source file' \\\n'debug:Commands for meant for debugging, prone to change' \\\n'experimental:Experimental commands are prone to change' \\\n'compile:Parse, resolve, lower into RQ & compile to SQL' \\\n'watch:Watch a directory and compile .prql files to .sql files' \\\n'list-targets:Show available compile target names' \\\n'lsp:Language Server Protocol' \\\n'shell-completion:Print a shell completion for supported shells' \\\n'help:Print this message or the help of the given subcommand(s)' \\\n    )\n    _describe -t commands 'prqlc commands' commands \"$@\"\n}\n(( $+functions[_prqlc__collect_commands] )) ||\n_prqlc__collect_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc collect commands' commands \"$@\"\n}\n(( $+functions[_prqlc__compile_commands] )) ||\n_prqlc__compile_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc compile commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug_commands] )) ||\n_prqlc__debug_commands() {\n    local commands; commands=(\n'annotate:Parse, resolve & combine source with comments annotating relation type' \\\n'lineage:Output column-level lineage graph' \\\n'ast:Print info about the AST data structure' \\\n'json-schema:Print JSON Schema' \\\n'help:Print this message or the help of the given subcommand(s)' \\\n    )\n    _describe -t commands 'prqlc debug commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__annotate_commands] )) ||\n_prqlc__debug__annotate_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc debug annotate commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__ast_commands] )) ||\n_prqlc__debug__ast_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc debug ast commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__help_commands] )) ||\n_prqlc__debug__help_commands() {\n    local commands; commands=(\n'annotate:Parse, resolve & combine source with comments annotating relation type' \\\n'lineage:Output column-level lineage graph' \\\n'ast:Print info about the AST data structure' \\\n'json-schema:Print JSON Schema' \\\n'help:Print this message or the help of the given subcommand(s)' \\\n    )\n    _describe -t commands 'prqlc debug help commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__help__annotate_commands] )) ||\n_prqlc__debug__help__annotate_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc debug help annotate commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__help__ast_commands] )) ||\n_prqlc__debug__help__ast_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc debug help ast commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__help__help_commands] )) ||\n_prqlc__debug__help__help_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc debug help help commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__help__json-schema_commands] )) ||\n_prqlc__debug__help__json-schema_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc debug help json-schema commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__help__lineage_commands] )) ||\n_prqlc__debug__help__lineage_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc debug help lineage commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__json-schema_commands] )) ||\n_prqlc__debug__json-schema_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc debug json-schema commands' commands \"$@\"\n}\n(( $+functions[_prqlc__debug__lineage_commands] )) ||\n_prqlc__debug__lineage_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc debug lineage commands' commands \"$@\"\n}\n(( $+functions[_prqlc__experimental_commands] )) ||\n_prqlc__experimental_commands() {\n    local commands; commands=(\n'doc:Generate Markdown documentation' \\\n'highlight:Syntax highlight' \\\n'help:Print this message or the help of the given subcommand(s)' \\\n    )\n    _describe -t commands 'prqlc experimental commands' commands \"$@\"\n}\n(( $+functions[_prqlc__experimental__doc_commands] )) ||\n_prqlc__experimental__doc_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc experimental doc commands' commands \"$@\"\n}\n(( $+functions[_prqlc__experimental__help_commands] )) ||\n_prqlc__experimental__help_commands() {\n    local commands; commands=(\n'doc:Generate Markdown documentation' \\\n'highlight:Syntax highlight' \\\n'help:Print this message or the help of the given subcommand(s)' \\\n    )\n    _describe -t commands 'prqlc experimental help commands' commands \"$@\"\n}\n(( $+functions[_prqlc__experimental__help__doc_commands] )) ||\n_prqlc__experimental__help__doc_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc experimental help doc commands' commands \"$@\"\n}\n(( $+functions[_prqlc__experimental__help__help_commands] )) ||\n_prqlc__experimental__help__help_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc experimental help help commands' commands \"$@\"\n}\n(( $+functions[_prqlc__experimental__help__highlight_commands] )) ||\n_prqlc__experimental__help__highlight_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc experimental help highlight commands' commands \"$@\"\n}\n(( $+functions[_prqlc__experimental__highlight_commands] )) ||\n_prqlc__experimental__highlight_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc experimental highlight commands' commands \"$@\"\n}\n(( $+functions[_prqlc__fmt_commands] )) ||\n_prqlc__fmt_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc fmt commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help_commands] )) ||\n_prqlc__help_commands() {\n    local commands; commands=(\n'parse:Parse into PL AST' \\\n'lex:Lex into Lexer Representation' \\\n'fmt:Parse & generate PRQL code back' \\\n'collect:Parse the whole project and collect it into a single PRQL source file' \\\n'debug:Commands for meant for debugging, prone to change' \\\n'experimental:Experimental commands are prone to change' \\\n'compile:Parse, resolve, lower into RQ & compile to SQL' \\\n'watch:Watch a directory and compile .prql files to .sql files' \\\n'list-targets:Show available compile target names' \\\n'lsp:Language Server Protocol' \\\n'shell-completion:Print a shell completion for supported shells' \\\n'help:Print this message or the help of the given subcommand(s)' \\\n    )\n    _describe -t commands 'prqlc help commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__collect_commands] )) ||\n_prqlc__help__collect_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help collect commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__compile_commands] )) ||\n_prqlc__help__compile_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help compile commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__debug_commands] )) ||\n_prqlc__help__debug_commands() {\n    local commands; commands=(\n'annotate:Parse, resolve & combine source with comments annotating relation type' \\\n'lineage:Output column-level lineage graph' \\\n'ast:Print info about the AST data structure' \\\n'json-schema:Print JSON Schema' \\\n    )\n    _describe -t commands 'prqlc help debug commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__debug__annotate_commands] )) ||\n_prqlc__help__debug__annotate_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help debug annotate commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__debug__ast_commands] )) ||\n_prqlc__help__debug__ast_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help debug ast commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__debug__json-schema_commands] )) ||\n_prqlc__help__debug__json-schema_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help debug json-schema commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__debug__lineage_commands] )) ||\n_prqlc__help__debug__lineage_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help debug lineage commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__experimental_commands] )) ||\n_prqlc__help__experimental_commands() {\n    local commands; commands=(\n'doc:Generate Markdown documentation' \\\n'highlight:Syntax highlight' \\\n    )\n    _describe -t commands 'prqlc help experimental commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__experimental__doc_commands] )) ||\n_prqlc__help__experimental__doc_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help experimental doc commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__experimental__highlight_commands] )) ||\n_prqlc__help__experimental__highlight_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help experimental highlight commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__fmt_commands] )) ||\n_prqlc__help__fmt_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help fmt commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__help_commands] )) ||\n_prqlc__help__help_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help help commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__lex_commands] )) ||\n_prqlc__help__lex_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help lex commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__list-targets_commands] )) ||\n_prqlc__help__list-targets_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help list-targets commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__lsp_commands] )) ||\n_prqlc__help__lsp_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help lsp commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__parse_commands] )) ||\n_prqlc__help__parse_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help parse commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__shell-completion_commands] )) ||\n_prqlc__help__shell-completion_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help shell-completion commands' commands \"$@\"\n}\n(( $+functions[_prqlc__help__watch_commands] )) ||\n_prqlc__help__watch_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc help watch commands' commands \"$@\"\n}\n(( $+functions[_prqlc__lex_commands] )) ||\n_prqlc__lex_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc lex commands' commands \"$@\"\n}\n(( $+functions[_prqlc__list-targets_commands] )) ||\n_prqlc__list-targets_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc list-targets commands' commands \"$@\"\n}\n(( $+functions[_prqlc__lsp_commands] )) ||\n_prqlc__lsp_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc lsp commands' commands \"$@\"\n}\n(( $+functions[_prqlc__parse_commands] )) ||\n_prqlc__parse_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc parse commands' commands \"$@\"\n}\n(( $+functions[_prqlc__shell-completion_commands] )) ||\n_prqlc__shell-completion_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc shell-completion commands' commands \"$@\"\n}\n(( $+functions[_prqlc__watch_commands] )) ||\n_prqlc__watch_commands() {\n    local commands; commands=()\n    _describe -t commands 'prqlc watch commands' commands \"$@\"\n}\n\nif [ \"$funcstack[1]\" = \"_prqlc\" ]; then\n    _prqlc \"$@\"\nelse\n    compdef _prqlc prqlc\nfi\n\n----- stderr -----\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion.snap",
    "content": "---\nsource: prqlc/prqlc/src/cli/test.rs\ninfo:\n  program: prqlc\n  args:\n    - \"--color=never\"\n    - shell-completion\n    - bash\n  env:\n    CLICOLOR_FORCE: \"\"\n    NO_COLOR: \"1\"\n    RUST_BACKTRACE: \"\"\n    RUST_LOG: \"\"\n---\nsuccess: true\nexit_code: 0\n----- stdout -----\n_prqlc() {\n    local i cur prev opts cmd\n    COMPREPLY=()\n    if [[ \"${BASH_VERSINFO[0]}\" -ge 4 ]]; then\n        cur=\"$2\"\n    else\n        cur=\"${COMP_WORDS[COMP_CWORD]}\"\n    fi\n    prev=\"$3\"\n    cmd=\"\"\n    opts=\"\"\n\n    for i in \"${COMP_WORDS[@]:0:COMP_CWORD}\"\n    do\n        case \"${cmd},${i}\" in\n            \",$1\")\n                cmd=\"prqlc\"\n                ;;\n            prqlc,collect)\n                cmd=\"prqlc__collect\"\n                ;;\n            prqlc,compile)\n                cmd=\"prqlc__compile\"\n                ;;\n            prqlc,debug)\n                cmd=\"prqlc__debug\"\n                ;;\n            prqlc,experimental)\n                cmd=\"prqlc__experimental\"\n                ;;\n            prqlc,fmt)\n                cmd=\"prqlc__fmt\"\n                ;;\n            prqlc,help)\n                cmd=\"prqlc__help\"\n                ;;\n            prqlc,lex)\n                cmd=\"prqlc__lex\"\n                ;;\n            prqlc,list-targets)\n                cmd=\"prqlc__list__targets\"\n                ;;\n            prqlc,lsp)\n                cmd=\"prqlc__lsp\"\n                ;;\n            prqlc,parse)\n                cmd=\"prqlc__parse\"\n                ;;\n            prqlc,shell-completion)\n                cmd=\"prqlc__shell__completion\"\n                ;;\n            prqlc,watch)\n                cmd=\"prqlc__watch\"\n                ;;\n            prqlc__debug,annotate)\n                cmd=\"prqlc__debug__annotate\"\n                ;;\n            prqlc__debug,ast)\n                cmd=\"prqlc__debug__ast\"\n                ;;\n            prqlc__debug,help)\n                cmd=\"prqlc__debug__help\"\n                ;;\n            prqlc__debug,json-schema)\n                cmd=\"prqlc__debug__json__schema\"\n                ;;\n            prqlc__debug,lineage)\n                cmd=\"prqlc__debug__lineage\"\n                ;;\n            prqlc__debug__help,annotate)\n                cmd=\"prqlc__debug__help__annotate\"\n                ;;\n            prqlc__debug__help,ast)\n                cmd=\"prqlc__debug__help__ast\"\n                ;;\n            prqlc__debug__help,help)\n                cmd=\"prqlc__debug__help__help\"\n                ;;\n            prqlc__debug__help,json-schema)\n                cmd=\"prqlc__debug__help__json__schema\"\n                ;;\n            prqlc__debug__help,lineage)\n                cmd=\"prqlc__debug__help__lineage\"\n                ;;\n            prqlc__experimental,doc)\n                cmd=\"prqlc__experimental__doc\"\n                ;;\n            prqlc__experimental,help)\n                cmd=\"prqlc__experimental__help\"\n                ;;\n            prqlc__experimental,highlight)\n                cmd=\"prqlc__experimental__highlight\"\n                ;;\n            prqlc__experimental__help,doc)\n                cmd=\"prqlc__experimental__help__doc\"\n                ;;\n            prqlc__experimental__help,help)\n                cmd=\"prqlc__experimental__help__help\"\n                ;;\n            prqlc__experimental__help,highlight)\n                cmd=\"prqlc__experimental__help__highlight\"\n                ;;\n            prqlc__help,collect)\n                cmd=\"prqlc__help__collect\"\n                ;;\n            prqlc__help,compile)\n                cmd=\"prqlc__help__compile\"\n                ;;\n            prqlc__help,debug)\n                cmd=\"prqlc__help__debug\"\n                ;;\n            prqlc__help,experimental)\n                cmd=\"prqlc__help__experimental\"\n                ;;\n            prqlc__help,fmt)\n                cmd=\"prqlc__help__fmt\"\n                ;;\n            prqlc__help,help)\n                cmd=\"prqlc__help__help\"\n                ;;\n            prqlc__help,lex)\n                cmd=\"prqlc__help__lex\"\n                ;;\n            prqlc__help,list-targets)\n                cmd=\"prqlc__help__list__targets\"\n                ;;\n            prqlc__help,lsp)\n                cmd=\"prqlc__help__lsp\"\n                ;;\n            prqlc__help,parse)\n                cmd=\"prqlc__help__parse\"\n                ;;\n            prqlc__help,shell-completion)\n                cmd=\"prqlc__help__shell__completion\"\n                ;;\n            prqlc__help,watch)\n                cmd=\"prqlc__help__watch\"\n                ;;\n            prqlc__help__debug,annotate)\n                cmd=\"prqlc__help__debug__annotate\"\n                ;;\n            prqlc__help__debug,ast)\n                cmd=\"prqlc__help__debug__ast\"\n                ;;\n            prqlc__help__debug,json-schema)\n                cmd=\"prqlc__help__debug__json__schema\"\n                ;;\n            prqlc__help__debug,lineage)\n                cmd=\"prqlc__help__debug__lineage\"\n                ;;\n            prqlc__help__experimental,doc)\n                cmd=\"prqlc__help__experimental__doc\"\n                ;;\n            prqlc__help__experimental,highlight)\n                cmd=\"prqlc__help__experimental__highlight\"\n                ;;\n            *)\n                ;;\n        esac\n    done\n\n    case \"${cmd}\" in\n        prqlc)\n            opts=\"-h -V --color --help --version parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__collect)\n            opts=\"-h --color --help [INPUT] [OUTPUT] [MAIN_PATH]\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__compile)\n            opts=\"-t -h --hide-signature-comment --no-format --target --debug-log --color --help [INPUT] [OUTPUT] [MAIN_PATH]\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --target)\n                    COMPREPLY=($(compgen -f \"${cur}\"))\n                    return 0\n                    ;;\n                -t)\n                    COMPREPLY=($(compgen -f \"${cur}\"))\n                    return 0\n                    ;;\n                --debug-log)\n                    COMPREPLY=($(compgen -f \"${cur}\"))\n                    return 0\n                    ;;\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug)\n            opts=\"-h --color --help annotate lineage ast json-schema help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__annotate)\n            opts=\"-h --color --help [INPUT] [OUTPUT] [MAIN_PATH]\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__ast)\n            opts=\"-h --color --help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__help)\n            opts=\"annotate lineage ast json-schema help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__help__annotate)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__help__ast)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__help__help)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__help__json__schema)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__help__lineage)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__json__schema)\n            opts=\"-h --ir-type --color --help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --ir-type)\n                    COMPREPLY=($(compgen -W \"pl rq lineage\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__debug__lineage)\n            opts=\"-h --format --color --help [INPUT] [OUTPUT] [MAIN_PATH]\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --format)\n                    COMPREPLY=($(compgen -W \"json yaml\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__experimental)\n            opts=\"-h --color --help doc highlight help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__experimental__doc)\n            opts=\"-h --format --color --help [INPUT] [OUTPUT] [MAIN_PATH]\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --format)\n                    COMPREPLY=($(compgen -W \"html markdown\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__experimental__help)\n            opts=\"doc highlight help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__experimental__help__doc)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__experimental__help__help)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__experimental__help__highlight)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__experimental__highlight)\n            opts=\"-h --color --help [INPUT] [OUTPUT] [MAIN_PATH]\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__fmt)\n            opts=\"-h --color --help [INPUT]\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help)\n            opts=\"parse lex fmt collect debug experimental compile watch list-targets lsp shell-completion help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__collect)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__compile)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__debug)\n            opts=\"annotate lineage ast json-schema\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__debug__annotate)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__debug__ast)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__debug__json__schema)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__debug__lineage)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__experimental)\n            opts=\"doc highlight\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__experimental__doc)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__experimental__highlight)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__fmt)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__help)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__lex)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__list__targets)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__lsp)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__parse)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__shell__completion)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__help__watch)\n            opts=\"\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__lex)\n            opts=\"-h --format --color --help [INPUT] [OUTPUT] [MAIN_PATH]\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --format)\n                    COMPREPLY=($(compgen -W \"json yaml\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__list__targets)\n            opts=\"-h --color --help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__lsp)\n            opts=\"-h --color --help\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__parse)\n            opts=\"-h --format --color --help [INPUT] [OUTPUT] [MAIN_PATH]\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --format)\n                    COMPREPLY=($(compgen -W \"json yaml\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__shell__completion)\n            opts=\"-h --color --help bash elvish fig fish nushell powershell zsh\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n        prqlc__watch)\n            opts=\"-h --no-format --no-signature --color --help <PATH>\"\n            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then\n                COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n                return 0\n            fi\n            case \"${prev}\" in\n                --color)\n                    COMPREPLY=($(compgen -W \"auto always never\" -- \"${cur}\"))\n                    return 0\n                    ;;\n                *)\n                    COMPREPLY=()\n                    ;;\n            esac\n            COMPREPLY=( $(compgen -W \"${opts}\" -- \"${cur}\") )\n            return 0\n            ;;\n    esac\n}\n\nif [[ \"${BASH_VERSINFO[0]}\" -eq 4 && \"${BASH_VERSINFO[1]}\" -ge 4 || \"${BASH_VERSINFO[0]}\" -gt 4 ]]; then\n    complete -F _prqlc -o nosort -o bashdefault -o default prqlc\nelse\n    complete -F _prqlc -o bashdefault -o default prqlc\nfi\n\n----- stderr -----\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/test.rs",
    "content": "use std::env::current_dir;\nuse std::fs;\nuse std::path::Path;\nuse std::path::PathBuf;\nuse std::process::Command;\nuse std::str::FromStr;\n\nuse insta_cmd::assert_cmd_snapshot;\nuse insta_cmd::get_cargo_bin;\nuse tempfile::TempDir;\nuse walkdir::WalkDir;\n\n#[cfg(not(windows))] // Windows has slightly different output (e.g. `prqlc.exe`), so we exclude.\n#[test]\nfn help() {\n    assert_cmd_snapshot!(prqlc_command().arg(\"--help\"), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    Usage: prqlc [OPTIONS] [COMMAND]\n\n    Commands:\n      parse             Parse into PL AST\n      lex               Lex into Lexer Representation\n      fmt               Parse & generate PRQL code back\n      collect           Parse the whole project and collect it into a single PRQL source file\n      debug             Commands for meant for debugging, prone to change\n      experimental      Experimental commands are prone to change\n      compile           Parse, resolve, lower into RQ & compile to SQL\n      watch             Watch a directory and compile .prql files to .sql files\n      list-targets      Show available compile target names\n      shell-completion  Print a shell completion for supported shells\n      help              Print this message or the help of the given subcommand(s)\n\n    Options:\n          --color <WHEN>  Controls when to use color [default: auto] [possible values: auto, always,\n                          never]\n      -h, --help          Print help\n      -V, --version       Print version\n\n    ----- stderr -----\n    \");\n\n    // without arguments\n    assert_cmd_snapshot!(prqlc_command(), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    Usage: prqlc [OPTIONS] [COMMAND]\n\n    Commands:\n      parse             Parse into PL AST\n      lex               Lex into Lexer Representation\n      fmt               Parse & generate PRQL code back\n      collect           Parse the whole project and collect it into a single PRQL source file\n      debug             Commands for meant for debugging, prone to change\n      experimental      Experimental commands are prone to change\n      compile           Parse, resolve, lower into RQ & compile to SQL\n      watch             Watch a directory and compile .prql files to .sql files\n      list-targets      Show available compile target names\n      shell-completion  Print a shell completion for supported shells\n      help              Print this message or the help of the given subcommand(s)\n\n    Options:\n          --color <WHEN>  Controls when to use color [default: auto] [possible values: auto, always,\n                          never]\n      -h, --help          Print help\n      -V, --version       Print version\n\n    ----- stderr -----\n    \");\n}\n\n#[test]\nfn get_targets() {\n    assert_cmd_snapshot!(prqlc_command().arg(\"list-targets\"), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    sql.any\n    sql.ansi\n    sql.bigquery\n    sql.clickhouse\n    sql.duckdb\n    sql.generic\n    sql.glaredb\n    sql.mssql\n    sql.mysql\n    sql.postgres\n    sql.redshift\n    sql.sqlite\n    sql.snowflake\n\n    ----- stderr -----\n    \");\n}\n\n#[test]\nfn compile() {\n    assert_cmd_snapshot!(prqlc_command()\n        .args([\"compile\", \"--hide-signature-comment\"])\n        .pass_stdin(\"from tracks\"), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    SELECT\n      *\n    FROM\n      tracks\n\n    ----- stderr -----\n    \");\n}\n\n#[cfg(not(windows))] // Windows has slightly different output (e.g. `prqlc.exe`), so we exclude.\n#[test]\nfn compile_help() {\n    assert_cmd_snapshot!(prqlc_command().args([\"compile\", \"--help\"]), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    Parse, resolve, lower into RQ & compile to SQL\n\n    Only displays the main pipeline and does not handle loop.\n\n    Usage: prqlc compile [OPTIONS] [INPUT] [OUTPUT] [MAIN_PATH]\n\n    Arguments:\n      [INPUT]\n              [default: -]\n\n      [OUTPUT]\n              [default: -]\n\n      [MAIN_PATH]\n              Identifier of the main pipeline\n\n    Options:\n          --hide-signature-comment\n              Exclude the signature comment containing the PRQL version\n\n          --no-format\n              Emit unformatted, dense SQL\n\n      -t, --target <TARGET>\n              Target to compile to\n              \n              [env: PRQLC_TARGET=]\n              [default: sql.any]\n\n          --debug-log <DEBUG_LOG>\n              File path into which to write the debug log to\n              \n              [env: PRQLC_DEBUG_LOG=]\n\n          --color <WHEN>\n              Controls when to use color\n              \n              [default: auto]\n              [possible values: auto, always, never]\n\n      -h, --help\n              Print help (see a summary with '-h')\n\n    ----- stderr -----\n    \");\n}\n\n#[test]\nfn long_query() {\n    assert_cmd_snapshot!(prqlc_command()\n        .args([\"compile\", \"--hide-signature-comment\", \"--debug-log=log_test.html\"])\n        .pass_stdin(r#\"\nlet long_query = (\n  from employees\n  filter gross_cost > 0\n  group {title} (\n      aggregate {\n          ct = count this,\n      }\n  )\n  sort ct\n  filter ct > 200\n  take 20\n  sort ct\n  filter ct > 200\n  take 20\n  sort ct\n  filter ct > 200\n  take 20\n  sort ct\n  filter ct > 200\n  take 20\n)\nfrom long_query\n  \"#), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    WITH table_2 AS (\n      SELECT\n        title,\n        COUNT(*) AS ct\n      FROM\n        employees\n      WHERE\n        gross_cost > 0\n      GROUP BY\n        title\n      HAVING\n        COUNT(*) > 200\n      ORDER BY\n        ct\n      LIMIT\n        20\n    ), table_1 AS (\n      SELECT\n        title,\n        ct\n      FROM\n        table_2\n      WHERE\n        ct > 200\n      ORDER BY\n        ct\n      LIMIT\n        20\n    ), table_0 AS (\n      SELECT\n        title,\n        ct\n      FROM\n        table_1\n      WHERE\n        ct > 200\n      ORDER BY\n        ct\n      LIMIT\n        20\n    ), long_query AS (\n      SELECT\n        title,\n        ct\n      FROM\n        table_0\n      WHERE\n        ct > 200\n      ORDER BY\n        ct\n      LIMIT\n        20\n    )\n    SELECT\n      title,\n      ct\n    FROM\n      long_query\n    ORDER BY\n      ct\n\n    ----- stderr -----\n    \");\n\n    // don't check the contents, they are very prone to change\n    assert!(PathBuf::from_str(\"./log_test.html\").unwrap().is_file());\n}\n\n#[test]\nfn compile_project() {\n    let mut cmd = prqlc_command();\n    cmd.args([\n        \"compile\",\n        \"--hide-signature-comment\",\n        \"--debug-log=log_test.json\",\n        project_path().to_str().unwrap(),\n        \"-\",\n        \"main\",\n    ]);\n\n    assert_cmd_snapshot!(cmd, @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    WITH table_1 AS (\n      SELECT\n        120 AS artist_id,\n        DATE '2023-05-18' AS last_listen\n      UNION\n      ALL\n      SELECT\n        7 AS artist_id,\n        DATE '2023-05-16' AS last_listen\n    ),\n    favorite_artists AS (\n      SELECT\n        artist_id,\n        last_listen\n      FROM\n        table_1\n    ),\n    table_0 AS (\n      SELECT\n        *\n      FROM\n        read_parquet('artists.parquet')\n    ),\n    input AS (\n      SELECT\n        *\n      FROM\n        table_0\n    )\n    SELECT\n      favorite_artists.artist_id,\n      favorite_artists.last_listen,\n      input.*\n    FROM\n      favorite_artists\n      LEFT OUTER JOIN input ON favorite_artists.artist_id = input.artist_id\n\n    ----- stderr -----\n    \");\n\n    // don't check the contents, they are very prone to change\n    assert!(PathBuf::from_str(\"./log_test.json\").unwrap().is_file());\n\n    assert_cmd_snapshot!(prqlc_command()\n      .args([\n        \"compile\",\n        \"--hide-signature-comment\",\n        project_path().to_str().unwrap(),\n        \"-\",\n        \"favorite_artists\",\n    ]), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    WITH table_0 AS (\n      SELECT\n        120 AS artist_id,\n        DATE '2023-05-18' AS last_listen\n      UNION\n      ALL\n      SELECT\n        7 AS artist_id,\n        DATE '2023-05-16' AS last_listen\n    )\n    SELECT\n      artist_id,\n      last_listen\n    FROM\n      table_0\n\n    ----- stderr -----\n    \");\n}\n\n#[test]\nfn format() {\n    // Test stdin formatting\n    assert_cmd_snapshot!(prqlc_command().args([\"fmt\"]).pass_stdin(\"from tracks | take 20\"), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    from tracks\n    take 20\n\n    ----- stderr -----\n    \");\n\n    // Test formatting a path:\n\n    // Create a temporary directory\n    let temp_dir = TempDir::new().expect(\"Failed to create temp directory\");\n\n    // Copy files from project_path() to temp_dir\n    copy_dir(&project_path(), temp_dir.path());\n\n    // Run fmt command on the temp directory\n    let _result = prqlc_command()\n        .args([\"fmt\", temp_dir.path().to_str().unwrap()])\n        .status()\n        .unwrap();\n\n    // Check if files in temp_dir match the original files\n    compare_directories(&project_path(), temp_dir.path());\n}\n\nfn copy_dir(src: &Path, dst: &Path) {\n    for entry in WalkDir::new(src) {\n        let entry = entry.unwrap();\n        let path = entry.path();\n        if path.is_file() {\n            let relative_path = path.strip_prefix(src).unwrap();\n            let target_path = dst.join(relative_path);\n            fs::create_dir_all(target_path.parent().unwrap()).unwrap();\n            fs::copy(path, target_path).unwrap();\n        }\n    }\n}\n\nfn compare_directories(dir1: &Path, dir2: &Path) {\n    for entry in WalkDir::new(dir1).into_iter().filter_map(|e| e.ok()) {\n        let path1 = entry.path();\n        if path1.is_file() {\n            let relative_path = path1.strip_prefix(dir1).unwrap();\n            let path2 = dir2.join(relative_path);\n\n            assert!(\n                path2.exists(),\n                \"File {relative_path:?} doesn't exist in the formatted directory\"\n            );\n\n            similar_asserts::assert_eq!(\n                fs::read_to_string(path1).unwrap(),\n                fs::read_to_string(path2).unwrap()\n            );\n        }\n    }\n}\n\n#[test]\nfn debug() {\n    assert_cmd_snapshot!(prqlc_command()\n        .args([\"debug\", \"lineage\"])\n        .pass_stdin(\"from tracks | select {artist, album}\"), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    frames:\n    - - 1:14-36\n      - columns:\n        - !Single\n          name:\n          - tracks\n          - artist\n          target_id: 118\n          target_name: null\n        - !Single\n          name:\n          - tracks\n          - album\n          target_id: 119\n          target_name: null\n        inputs:\n        - id: 116\n          name: tracks\n          table:\n          - default_db\n          - tracks\n    nodes:\n    - id: 116\n      kind: Ident\n      span: 1:0-11\n      ident: !Ident\n      - default_db\n      - tracks\n      parent: 121\n    - id: 118\n      kind: Ident\n      span: 1:22-28\n      ident: !Ident\n      - this\n      - tracks\n      - artist\n      targets:\n      - 116\n      parent: 120\n    - id: 119\n      kind: Ident\n      span: 1:30-35\n      ident: !Ident\n      - this\n      - tracks\n      - album\n      targets:\n      - 116\n      parent: 120\n    - id: 120\n      kind: Tuple\n      span: 1:21-36\n      children:\n      - 118\n      - 119\n      parent: 121\n    - id: 121\n      kind: 'TransformCall: Select'\n      span: 1:14-36\n      children:\n      - 116\n      - 120\n    ast:\n      name: Project\n      stmts:\n      - VarDef:\n          kind: Main\n          name: main\n          value:\n            Pipeline:\n              exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                    - from\n                    span: 1:0-4\n                  args:\n                  - Ident:\n                    - tracks\n                    span: 1:5-11\n                span: 1:0-11\n              - FuncCall:\n                  name:\n                    Ident:\n                    - select\n                    span: 1:14-20\n                  args:\n                  - Tuple:\n                    - Ident:\n                      - artist\n                      span: 1:22-28\n                    - Ident:\n                      - album\n                      span: 1:30-35\n                    span: 1:21-36\n                span: 1:14-36\n            span: 1:0-36\n        span: 1:0-36\n\n    ----- stderr -----\n    \");\n\n    // Don't test the output of this, since on one min-versions check it had\n    // different results, and didn't repro on Mac. It having different results\n    // makes it difficult to debug, and we get most of the value by just\n    // checking it runs successfully.\n    prqlc_command()\n        .args([\"debug\", \"ast\"])\n        .stdout(std::process::Stdio::null())\n        .stderr(std::process::Stdio::null())\n        .status()\n        .unwrap();\n}\n\n// The output of `prqlc debug json-schema` is long, so rather than\n// comparing the full output as a snapshot, we just verify that the\n// standard output parses as JSON and check a couple top-level keys.\n#[test]\nfn debug_json_schema() {\n    use serde_json::Value;\n\n    let output = prqlc_command()\n        .args([\"debug\", \"json-schema\", \"--ir-type\", \"pl\"])\n        .output()\n        .unwrap();\n\n    assert!(output.status.success());\n\n    let stdout = std::str::from_utf8(&output.stdout).unwrap();\n    let parsed: Value = serde_json::from_str(stdout).unwrap();\n\n    assert_eq!(\n        parsed[\"$schema\"],\n        \"https://json-schema.org/draft/2020-12/schema\"\n    );\n    assert_eq!(parsed[\"type\"], \"object\");\n    assert_eq!(parsed[\"title\"], \"ModuleDef\");\n}\n\n#[test]\nfn shell_completion() {\n    for shell in [\"bash\", \"fish\", \"powershell\", \"zsh\"].iter() {\n        assert_cmd_snapshot!(prqlc_command().args([\"shell-completion\", shell]));\n    }\n}\n\nfn project_path() -> PathBuf {\n    current_dir()\n        .unwrap()\n        // We canonicalize so that it doesn't matter where the cwd is.\n        .canonicalize()\n        .unwrap()\n        .join(\"tests/integration/project\")\n}\n\nfn prqlc_command() -> Command {\n    let mut cmd = Command::new(get_cargo_bin(\"prqlc\"));\n    normalize_prqlc(&mut cmd);\n    cmd\n}\n\nfn normalize_prqlc(cmd: &mut Command) -> &mut Command {\n    cmd\n        // We set `CLICOLOR_FORCE` in CI to force color output, but we don't want `prqlc` to\n        // output color for our snapshot tests. And it seems to override the\n        // `--color=never` flag.\n        .env_remove(\"CLICOLOR_FORCE\")\n        .env(\"NO_COLOR\", \"1\")\n        .args([\"--color=never\"])\n        // We don't want the tests to be affected by the user's `RUST_BACKTRACE` setting.\n        .env_remove(\"RUST_BACKTRACE\")\n        .env_remove(\"RUST_LOG\")\n}\n\n#[test]\nfn compile_no_prql_files() {\n    assert_cmd_snapshot!(prqlc_command().args([\"compile\", \"README.md\"]), @r\"\n    success: false\n    exit_code: 1\n    ----- stdout -----\n\n    ----- stderr -----\n    Error: No `.prql` files found in the source tree\n    \");\n}\n\n#[test]\nfn lex() {\n    assert_cmd_snapshot!(prqlc_command().args([\"lex\"]).pass_stdin(\"from tracks\"), @r\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    - kind: Start\n      span:\n        start: 0\n        end: 0\n    - kind: !Ident from\n      span:\n        start: 0\n        end: 4\n    - kind: !Ident tracks\n      span:\n        start: 5\n        end: 11\n\n    ----- stderr -----\n    \");\n\n    assert_cmd_snapshot!(prqlc_command().args([\"lex\", \"--format=json\"]).pass_stdin(\"from tracks\"), @r#\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    [\n      {\n        \"kind\": \"Start\",\n        \"span\": {\n          \"start\": 0,\n          \"end\": 0\n        }\n      },\n      {\n        \"kind\": {\n          \"Ident\": \"from\"\n        },\n        \"span\": {\n          \"start\": 0,\n          \"end\": 4\n        }\n      },\n      {\n        \"kind\": {\n          \"Ident\": \"tracks\"\n        },\n        \"span\": {\n          \"start\": 5,\n          \"end\": 11\n        }\n      }\n    ]\n    ----- stderr -----\n    \"#);\n}\n\n#[cfg(feature = \"lsp\")]\n#[test]\nfn lsp() {\n    let init = serde_json::to_string(&lsp_server::Message::Request(lsp_server::Request {\n        method: \"initialize\".into(),\n        id: lsp_server::RequestId::from(1),\n        params: serde_json::json!({\"capabilities\": {}}),\n    }))\n    .unwrap();\n    let initialized = serde_json::to_string(&lsp_server::Message::Notification(\n        lsp_server::Notification {\n            method: \"initialized\".into(),\n            params: serde_json::json!({}),\n        },\n    ))\n    .unwrap();\n    let ex1 = serde_json::to_string(&lsp_server::Message::Notification(\n        lsp_server::Notification {\n            method: \"exit\".into(),\n            params: serde_json::Value::Null,\n        },\n    ))\n    .unwrap();\n\n    assert_cmd_snapshot!(prqlc_command().args([\"lsp\"])\n        .pass_stdin(format!(\"Content-Length: {}\\r\\n\\r\\n{}Content-Length: {}\\r\\n\\r\\n{}Content-Length: {}\\r\\n\\r\\n{}\",\n            init.len(), init,\n            initialized.len(), initialized,\n            ex1.len(), ex1))\n        , @r###\"\n    success: true\n    exit_code: 0\n    ----- stdout -----\n    Content-Length: 78\n\n    {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"capabilities\":{\"definitionProvider\":true}}}\n    ----- stderr -----\n    starting PRQL LSP server\n    starting main loop\n    got msg: Notification(Notification { method: \"exit\", params: Null })\n    got notification: Notification { method: \"exit\", params: Null }\n    shutting down server\n    \"###);\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/cli/watch.rs",
    "content": "use std::ffi::{OsStr, OsString};\nuse std::fs;\nuse std::path::Path;\n\nuse anyhow::{anyhow, Result};\nuse clap::Parser;\nuse notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};\nuse walkdir::WalkDir;\n\nuse super::jinja;\n\n#[derive(Parser, Debug, Clone)]\npub struct WatchArgs {\n    /// Directory or file to watch for changes\n    pub path: OsString,\n\n    #[arg(long, default_value_t = false)]\n    pub no_format: bool,\n\n    #[arg(long, default_value_t = false)]\n    pub no_signature: bool,\n}\n\npub fn run(command: &mut WatchArgs) -> Result<()> {\n    let opt = prqlc::Options {\n        format: !command.no_format,\n        signature_comment: !command.no_signature,\n        ..Default::default()\n    };\n    let path = Path::new(&command.path);\n\n    // initial compile\n    find_and_compile(path, &opt)?;\n\n    // watch and compile\n    println!(\"Watching path \\\"{}\\\"\", path.display());\n    watch_and_compile(path, &opt)?;\n\n    Ok(())\n}\n\nfn find_and_compile(path: &Path, opt: &prqlc::Options) -> Result<()> {\n    for entry in WalkDir::new(path) {\n        compile_path(entry?.path(), opt)?;\n    }\n\n    Ok(())\n}\n\nfn watch_and_compile(path: &Path, opt: &prqlc::Options) -> Result<()> {\n    let cwd = std::env::current_dir().ok();\n\n    let (tx, rx) = std::sync::mpsc::channel();\n\n    // Automatically select the best implementation for current platform.\n    let mut watcher = RecommendedWatcher::new(tx, Config::default())?;\n\n    // Add a path to be watched. All files and directories at that path and\n    // below will be monitored for changes.\n    watcher.watch(path, RecursiveMode::Recursive)?;\n\n    for res in rx {\n        match res {\n            Ok(event) => match event.kind {\n                notify::EventKind::Any\n                | notify::EventKind::Create(\n                    notify::event::CreateKind::File\n                    | notify::event::CreateKind::Any\n                    | notify::event::CreateKind::Other,\n                )\n                | notify::EventKind::Modify(_) => {\n                    for path in event.paths {\n                        // to make display nicer, try to convert to relative paths\n                        let relative_path = if let Some(cwd) = &cwd {\n                            path.strip_prefix(cwd).unwrap_or(&path)\n                        } else {\n                            &path\n                        };\n\n                        let _ignore = compile_path(relative_path, opt);\n                    }\n                }\n\n                notify::EventKind::Access(_)\n                | notify::EventKind::Create(notify::event::CreateKind::Folder)\n                | notify::EventKind::Remove(_)\n                | notify::EventKind::Other => {}\n            },\n            Err(e) => println!(\"watch error: {e:?}\"),\n        }\n    }\n\n    Ok(())\n}\n\nfn compile_path(path: &Path, opt: &prqlc::Options) -> Result<()> {\n    // filter to only .prql files\n    if path.extension() != Some(OsStr::new(\"prql\")) {\n        return Ok(());\n    }\n\n    let sql_path = path.with_extension(\"sql\");\n    let prql_path = path;\n\n    // read\n    let Some(prql_string) = fs::read_to_string(prql_path).ok() else {\n        // file may not exist, because this may have been a delete event\n        return Ok(());\n    };\n    if prql_string.is_empty() {\n        return Ok(());\n    }\n\n    // pre-process Jinja\n    let (prql_string, jinja_context) = jinja::pre_process(&prql_string)?;\n\n    // compile\n    println!(\"Compiling {}\", prql_path.display());\n    let sql_string = match prqlc::compile(&prql_string, opt) {\n        Ok(sql_string) => sql_string,\n        Err(errs) => {\n            for err in errs.inner {\n                println!(\"{err}\");\n            }\n            return Err(anyhow!(\"failed to compile\"));\n        }\n    };\n\n    // post-process Jinja\n    let sql_string = jinja::post_process(&sql_string, jinja_context);\n\n    // write\n    fs::write(sql_path, sql_string)?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/codegen/ast.rs",
    "content": "use std::borrow::Cow;\nuse std::collections::HashSet;\nuse std::sync::OnceLock;\n\nuse regex::Regex;\n\nuse super::{WriteOpt, WriteSource};\nuse crate::codegen::SeparatedExprs;\nuse crate::pr;\n\npub(crate) fn write_expr(expr: &pr::Expr) -> String {\n    expr.write(WriteOpt::new_width(u16::MAX)).unwrap()\n}\n\nfn write_within<T: WriteSource>(\n    node: &T,\n    parent: &pr::ExprKind,\n    mut opt: WriteOpt,\n) -> Option<String> {\n    let parent_strength = binding_strength(parent);\n    opt.context_strength = opt.context_strength.max(parent_strength);\n\n    node.write(opt)\n}\n\nimpl WriteSource for pr::Expr {\n    fn write(&self, mut opt: WriteOpt) -> Option<String> {\n        let mut r = String::new();\n\n        if let Some(alias) = &self.alias {\n            r += opt.consume(&write_ident_part(alias))?;\n            r += opt.consume(\" = \")?;\n            opt.unbound_expr = false;\n        }\n\n        if !needs_parenthesis(self, &opt) {\n            r += &self.kind.write(opt.clone())?;\n        } else {\n            let value = self.kind.write_between(\"(\", \")\", opt.clone());\n\n            if let Some(value) = value {\n                r += &value;\n            } else {\n                r += &break_line_within_parenthesis(&self.kind, opt)?;\n            }\n        };\n        Some(r)\n    }\n}\n\nfn needs_parenthesis(this: &pr::Expr, opt: &WriteOpt) -> bool {\n    if opt.unbound_expr && can_bind_left(&this.kind) {\n        return true;\n    }\n\n    let binding_strength = binding_strength(&this.kind);\n    if opt.context_strength > binding_strength {\n        // parent has higher binding strength, which means it would \"steal\" operand of this expr\n        // => parenthesis are needed\n        return true;\n    }\n\n    if opt.context_strength < binding_strength {\n        // parent has higher binding strength, which means it would \"steal\" operand of this expr\n        // => parenthesis are needed\n        return false;\n    }\n\n    // parent has equal binding strength, which means that now associativity of this expr counts\n    // for example:\n    //   this=(a + b), parent=(a + b) + c\n    //   asoc of + is left\n    //   this is the left operand of parent\n    //   => assoc_matches=true => we don't need parenthesis\n\n    //   this=(a + b), parent=c + (a + b)\n    //   asoc of + is left\n    //   this is the right operand of parent\n    //   => assoc_matches=false => we need parenthesis\n    let assoc_matches = match opt.binary_position {\n        super::Position::Left => associativity(&this.kind) == super::Position::Left,\n        super::Position::Right => associativity(&this.kind) == super::Position::Right,\n        super::Position::Unspecified => false,\n    };\n\n    !assoc_matches\n}\n\nimpl WriteSource for pr::ExprKind {\n    fn write(&self, mut opt: WriteOpt) -> Option<String> {\n        use pr::ExprKind::*;\n\n        match &self {\n            Ident(ident) => Some(ident.to_string()),\n\n            Pipeline(pipeline) => SeparatedExprs {\n                inline: \" | \",\n                line_end: \"\",\n                exprs: &pipeline.exprs,\n            }\n            .write_between(\"(\", \")\", opt),\n\n            Tuple(fields) => SeparatedExprs {\n                exprs: fields,\n                inline: \", \",\n                line_end: \",\",\n            }\n            .write_between(\"{\", \"}\", opt),\n\n            Array(items) => SeparatedExprs {\n                exprs: items,\n                inline: \", \",\n                line_end: \",\",\n            }\n            .write_between(\"[\", \"]\", opt),\n\n            Range(range) => {\n                let mut r = String::new();\n                if let Some(start) = &range.start {\n                    let start = write_within(start.as_ref(), self, opt.clone())?;\n                    r += opt.consume(&start)?;\n                }\n\n                r += opt.consume(\"..\")?;\n\n                if let Some(end) = &range.end {\n                    r += &write_within(end.as_ref(), self, opt)?;\n                }\n                Some(r)\n            }\n            Binary(pr::BinaryExpr { op, left, right }) => {\n                let mut r = String::new();\n\n                let mut opt_left = opt.clone();\n                opt_left.binary_position = super::Position::Left;\n                let left = write_within(left.as_ref(), self, opt_left)?;\n                r += opt.consume(&left)?;\n\n                r += opt.consume(\" \")?;\n                r += opt.consume(&op.to_string())?;\n                r += opt.consume(\" \")?;\n\n                let mut opt_right = opt;\n                opt_right.binary_position = super::Position::Right;\n                r += &write_within(right.as_ref(), self, opt_right)?;\n                Some(r)\n            }\n            Unary(pr::UnaryExpr { op, expr }) => {\n                let mut r = String::new();\n\n                r += opt.consume(&op.to_string())?;\n                r += &write_within(expr.as_ref(), self, opt)?;\n                Some(r)\n            }\n            FuncCall(func_call) => {\n                let mut r = String::new();\n\n                let name = write_within(func_call.name.as_ref(), self, opt.clone())?;\n                r += opt.consume(&name)?;\n                opt.unbound_expr = true;\n\n                for (name, arg) in &func_call.named_args {\n                    r += opt.consume(\" \")?;\n\n                    r += opt.consume(name)?;\n\n                    r += opt.consume(\":\")?;\n\n                    let arg = write_within(arg, self, opt.clone())?;\n                    r += opt.consume(&arg)?;\n                }\n                for arg in &func_call.args {\n                    r += opt.consume(\" \")?;\n\n                    let arg = write_within(arg, self, opt.clone())?;\n                    r += opt.consume(&arg)?;\n                }\n                Some(r)\n            }\n            Func(c) => {\n                let mut r = \"func \".to_string();\n\n                for param in &c.params {\n                    r += opt.consume(&write_ident_part(&param.name))?;\n                    r += opt.consume(\" \")?;\n                    if let Some(ty) = &param.ty {\n                        let ty = ty.write_between(\"<\", \">\", opt.clone())?;\n                        r += opt.consume(&ty)?;\n                        r += opt.consume(\" \")?;\n                    }\n                }\n                for param in &c.named_params {\n                    r += opt.consume(&write_ident_part(&param.name))?;\n                    r += opt.consume(\":\")?;\n                    r += opt.consume(&param.default_value.as_ref().unwrap().write(opt.clone())?)?;\n                    r += opt.consume(\" \")?;\n                }\n                r += opt.consume(\"-> \")?;\n\n                if let Some(ty) = &c.return_ty {\n                    let ty = ty.write_between(\"<\", \">\", opt.clone())?;\n                    r += opt.consume(&ty)?;\n                    r += opt.consume(\" \")?;\n                }\n\n                // try a single line\n                if let Some(body) = c.body.write(opt.clone()) {\n                    r += &body;\n                } else {\n                    r += &break_line_within_parenthesis(c.body.as_ref(), opt)?;\n                }\n\n                Some(r)\n            }\n            SString(parts) => display_interpolation(\"s\", parts, opt),\n            FString(parts) => display_interpolation(\"f\", parts, opt),\n            Literal(literal) => opt.consume(literal.to_string()),\n            Case(cases) => {\n                let mut r = String::new();\n                r += \"case \";\n                r += &SeparatedExprs {\n                    exprs: cases,\n                    inline: \", \",\n                    line_end: \",\",\n                }\n                .write_between(\"[\", \"]\", opt)?;\n                Some(r)\n            }\n            Param(id) => Some(format!(\"${id}\")),\n            Internal(operator_name) => Some(format!(\"internal {operator_name}\")),\n        }\n    }\n}\n\nfn break_line_within_parenthesis<T: WriteSource>(expr: &T, mut opt: WriteOpt) -> Option<String> {\n    let mut r = \"(\\n\".to_string();\n    opt.indent += 1;\n    r += &opt.write_indent();\n    opt.reset_line()?;\n    r += &expr.write(opt.clone())?;\n    r += \"\\n\";\n    opt.indent -= 1;\n    r += &opt.write_indent();\n    r += \")\";\n    Some(r)\n}\n\nfn binding_strength(expr: &pr::ExprKind) -> u8 {\n    match expr {\n        // For example, if it's an Ident, it's basically infinite — a simple\n        // ident never needs parentheses around it.\n        pr::ExprKind::Ident(_) => 100,\n\n        // Stronger than a range, since `-1..2` is `(-1)..2`\n        // Stronger than binary op, since `-x == y` is `(-x) == y`\n        // Stronger than a func call, since `exists !y` is `exists (!y)`\n        pr::ExprKind::Unary(..) => 20,\n\n        pr::ExprKind::Range(_) => 19,\n\n        pr::ExprKind::Binary(pr::BinaryExpr { op, .. }) => match op {\n            pr::BinOp::Pow => 19,\n            pr::BinOp::Mul | pr::BinOp::DivInt | pr::BinOp::DivFloat | pr::BinOp::Mod => 18,\n            pr::BinOp::Add | pr::BinOp::Sub => 17,\n            pr::BinOp::Eq\n            | pr::BinOp::Ne\n            | pr::BinOp::Gt\n            | pr::BinOp::Lt\n            | pr::BinOp::Gte\n            | pr::BinOp::Lte\n            | pr::BinOp::RegexSearch => 16,\n            pr::BinOp::Coalesce => 15,\n            pr::BinOp::And => 14,\n            pr::BinOp::Or => 13,\n        },\n\n        // Weaker than a child assign, since `select x = 1`\n        // Weaker than a binary operator, since `filter x == 1`\n        pr::ExprKind::FuncCall(_) => 10,\n        // ExprKind::FuncCall(_) if !is_parent => 2,\n        pr::ExprKind::Func(_) => 7,\n\n        // other nodes should not contain any inner exprs\n        _ => 100,\n    }\n}\n\nfn associativity(expr: &pr::ExprKind) -> super::Position {\n    match expr {\n        pr::ExprKind::Binary(pr::BinaryExpr { op, .. }) => match op {\n            pr::BinOp::Pow => super::Position::Right,\n            pr::BinOp::Eq\n            | pr::BinOp::Ne\n            | pr::BinOp::Gt\n            | pr::BinOp::Lt\n            | pr::BinOp::Gte\n            | pr::BinOp::Lte\n            | pr::BinOp::RegexSearch => super::Position::Unspecified,\n            _ => super::Position::Left,\n        },\n\n        _ => super::Position::Unspecified,\n    }\n}\n\n/// True if this expression could be mistakenly bound with an expression on the left.\nfn can_bind_left(expr: &pr::ExprKind) -> bool {\n    matches!(\n        expr,\n        pr::ExprKind::Unary(pr::UnaryExpr {\n            op: pr::UnOp::EqSelf | pr::UnOp::Add | pr::UnOp::Neg,\n            ..\n        })\n    )\n}\n\nimpl WriteSource for pr::Ident {\n    fn write(&self, mut opt: WriteOpt) -> Option<String> {\n        let width = self.path.iter().map(|p| p.len() + 1).sum::<usize>() + self.name.len();\n        opt.consume_width(width as u16)?;\n\n        let mut r = String::new();\n        for part in &self.path {\n            r += &write_ident_part(part);\n            r += \".\";\n        }\n        r += &write_ident_part(&self.name);\n        Some(r)\n    }\n}\n\nfn keywords() -> &'static HashSet<&'static str> {\n    static KEYWORDS: OnceLock<HashSet<&'static str>> = OnceLock::new();\n    KEYWORDS.get_or_init(|| {\n        HashSet::from_iter([\n            \"let\", \"into\", \"case\", \"prql\", \"type\", \"module\", \"internal\", \"func\",\n        ])\n    })\n}\n\nfn valid_prql_ident() -> &'static Regex {\n    static VALID_PRQL_IDENT: OnceLock<Regex> = OnceLock::new();\n    VALID_PRQL_IDENT.get_or_init(|| {\n        // Pomsky expression (regex is to Pomsky what SQL is to PRQL):\n        // ^ ('*' | [ascii_alpha '_$'] [ascii_alpha ascii_digit '_$']* ) $\n        Regex::new(r\"^(?:\\*|[a-zA-Z_$][a-zA-Z0-9_$]*)$\").unwrap()\n    })\n}\n\npub fn write_ident_part(s: &str) -> Cow<'_, str> {\n    if valid_prql_ident().is_match(s) && !keywords().contains(s) {\n        s.into()\n    } else {\n        format!(\"`{s}`\").into()\n    }\n}\n\nimpl WriteSource for Vec<pr::Stmt> {\n    fn write(&self, mut opt: WriteOpt) -> Option<String> {\n        opt.reset_line()?;\n\n        let mut r = String::new();\n        for stmt in self {\n            if !r.is_empty() {\n                r += \"\\n\";\n            }\n\n            r += &opt.write_indent();\n            r += &stmt.write_or_expand(opt.clone());\n        }\n        Some(r)\n    }\n}\n\nimpl WriteSource for pr::Stmt {\n    fn write(&self, mut opt: WriteOpt) -> Option<String> {\n        let mut r = String::new();\n\n        for annotation in &self.annotations {\n            r += \"@\";\n            r += &annotation.expr.write(opt.clone())?;\n            r += \"\\n\";\n            r += &opt.write_indent();\n            opt.reset_line()?;\n        }\n\n        match &self.kind {\n            pr::StmtKind::QueryDef(query) => {\n                r += \"prql\";\n                if let Some(version) = &query.version {\n                    r += &format!(r#\" version:\"{version}\"\"#);\n                }\n                for (key, value) in &query.other {\n                    r += &format!(\" {key}:{value}\");\n                }\n                r += \"\\n\";\n            }\n            pr::StmtKind::VarDef(var_def) => match var_def.kind {\n                _ if var_def.value.is_none() || var_def.ty.is_some() => {\n                    let typo = if let Some(ty) = &var_def.ty {\n                        format!(\"<{}> \", ty.write(opt.clone())?)\n                    } else {\n                        \"\".to_string()\n                    };\n\n                    r += opt.consume(&format!(\"let {} {}\", var_def.name, typo))?;\n\n                    if let Some(val) = &var_def.value {\n                        r += opt.consume(\"= \")?;\n                        r += &val.write(opt)?;\n                    }\n                    r += \"\\n\";\n                }\n\n                pr::VarDefKind::Let => {\n                    r += opt.consume(&format!(\"let {} = \", var_def.name))?;\n\n                    r += &var_def.value.as_ref().unwrap().write(opt)?;\n                    r += \"\\n\";\n                }\n                pr::VarDefKind::Into | pr::VarDefKind::Main => {\n                    let val = var_def.value.as_ref().unwrap();\n                    match &val.kind {\n                        pr::ExprKind::Pipeline(pipeline) => {\n                            for expr in &pipeline.exprs {\n                                r += &expr.write(opt.clone())?;\n                                r += \"\\n\";\n                            }\n                        }\n                        _ => {\n                            r += &val.write(opt)?;\n                            r += \"\\n\";\n                        }\n                    }\n\n                    if var_def.kind == pr::VarDefKind::Into {\n                        r += &format!(\"into {}\", var_def.name);\n                        r += \"\\n\";\n                    }\n                }\n            },\n            pr::StmtKind::TypeDef(type_def) => {\n                r += opt.consume(&format!(\"type {}\", type_def.name))?;\n                r += opt.consume(\" = \")?;\n                r += &type_def.value.kind.write(opt)?;\n                r += \"\\n\";\n            }\n            pr::StmtKind::ModuleDef(module_def) => {\n                r += &format!(\"module {} {{\\n\", module_def.name);\n                opt.indent += 1;\n\n                r += &module_def.stmts.write(opt.clone())?;\n\n                opt.indent -= 1;\n                r += &opt.write_indent();\n                r += \"}\\n\";\n            }\n            pr::StmtKind::ImportDef(import_def) => {\n                r += \"import \";\n                if let Some(alias) = &import_def.alias {\n                    r += &write_ident_part(alias);\n                    r += \" = \";\n                }\n                r += &import_def.name.write(opt)?;\n                r += \"\\n\";\n            }\n        }\n        Some(r)\n    }\n}\n\nfn display_interpolation(\n    prefix: &str,\n    parts: &[pr::InterpolateItem],\n    opt: WriteOpt,\n) -> Option<String> {\n    let mut r = String::new();\n    r += prefix;\n    r += \"\\\"\";\n    for part in parts {\n        match &part {\n            // We use double braces to escape braces and backslash-quote to escape quotes\n            pr::InterpolateItem::String(s) => {\n                r += s\n                    .replace('\\\\', \"\\\\\\\\\")\n                    .replace('\"', \"\\\\\\\"\")\n                    .replace('{', \"{{\")\n                    .replace('}', \"}}\")\n                    .as_str()\n            }\n            pr::InterpolateItem::Expr { expr, .. } => {\n                r += \"{\";\n                r += &expr.write(opt.clone())?;\n                r += \"}\"\n            }\n        }\n    }\n    r += \"\\\"\";\n    Some(r)\n}\n\nimpl WriteSource for pr::SwitchCase {\n    fn write(&self, opt: WriteOpt) -> Option<String> {\n        let mut r = String::new();\n        r += &self.condition.write(opt.clone())?;\n        r += \" => \";\n        r += &self.value.write(opt)?;\n        Some(r)\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use insta::assert_snapshot;\n\n    use super::*;\n\n    #[track_caller]\n    fn assert_is_formatted(input: &str) {\n        let formatted = format_single_stmt(input);\n        similar_asserts::assert_eq!(input.trim(), formatted.trim());\n    }\n\n    fn format_single_stmt(query: &str) -> String {\n        use itertools::Itertools;\n        let stmt = crate::prql_to_pl(query)\n            .unwrap()\n            .stmts\n            .into_iter()\n            .exactly_one()\n            .unwrap();\n        stmt.write(WriteOpt::default()).unwrap()\n    }\n\n    #[test]\n    fn test_pipeline() {\n        let short = pr::Expr::new(pr::ExprKind::Ident(pr::Ident::from_name(\n            \"short\".to_string(),\n        )));\n        let long = pr::Expr::new(pr::ExprKind::Ident(pr::Ident::from_name(\n            \"some_really_long_and_really_long_name\".to_string(),\n        )));\n\n        let mut opt = WriteOpt {\n            indent: 1,\n            ..Default::default()\n        };\n\n        // short pipelines should be inlined\n        let pipeline = pr::Expr::new(pr::ExprKind::Pipeline(pr::Pipeline {\n            exprs: vec![short.clone(), short.clone(), short.clone()],\n        }));\n        assert_snapshot!(pipeline.write(opt.clone()).unwrap(), @\"(short | short | short)\");\n\n        // long pipelines should be indented\n        let pipeline = pr::Expr::new(pr::ExprKind::Pipeline(pr::Pipeline {\n            exprs: vec![short.clone(), long.clone(), long, short.clone()],\n        }));\n        // colons are a workaround to avoid trimming\n        assert_snapshot!(pipeline.write(opt.clone()).unwrap(), @r\"\n        (\n            short\n            some_really_long_and_really_long_name\n            some_really_long_and_really_long_name\n            short\n          )\n        \");\n\n        // sometimes, there is just not enough space\n        opt.rem_width = 4;\n        opt.indent = 100;\n        let pipeline = pr::Expr::new(pr::ExprKind::Pipeline(pr::Pipeline { exprs: vec![short] }));\n        assert!(pipeline.write(opt).is_none());\n    }\n\n    #[test]\n    fn test_escaped_string() {\n        assert_is_formatted(r#\"filter name ~= \"\\\\(I Can't Help\\\\) Falling\"\"#);\n    }\n\n    #[test]\n    fn test_double_braces() {\n        assert_is_formatted(\n            r#\"let has_valid_title = s\"regexp_contains(title, '([a-z0-9]*-){{2,}}')\"\"#,\n        );\n    }\n\n    #[test]\n    fn test_sstring_escaped_quotes() {\n        // Test that escaped quotes in s-strings round-trip correctly (issue #5496)\n        assert_is_formatted(r#\"from s\"SELECT \\\"col1 foo\\\"\"\"#);\n    }\n\n    #[test]\n    fn test_unary() {\n        assert_is_formatted(r#\"sort {-duration}\"#);\n\n        assert_is_formatted(r#\"select a = -b\"#);\n        assert_is_formatted(r#\"join `project-bar.dataset.table` (==col_bax)\"#);\n    }\n\n    #[test]\n    fn test_binary() {\n        assert_is_formatted(r#\"let a = 5 * (4 + 3) ?? 5 / 2 // 2 == 1 and true\"#);\n\n        assert_is_formatted(r#\"let a = 5 / 2 / 2\"#);\n        assert_is_formatted(r#\"let a = 5 / (2 / 2)\"#);\n\n        assert_is_formatted(r#\"let a = (5 ** 2) ** 2\"#);\n        assert_is_formatted(r#\"let a = 5 ** 2 ** 2\"#);\n    }\n\n    #[test]\n    fn test_func() {\n        assert_is_formatted(r#\"let a = func x y:false -> x and y\"#);\n    }\n\n    #[test]\n    fn test_simple() {\n        assert_is_formatted(\n            r#\"\naggregate average_country_salary = (\n  average salary\n)\"#,\n        );\n    }\n\n    #[test]\n    fn test_alias() {\n        assert_is_formatted(\n            r#\"\nfrom artists\nselect {`customer name` = foo, x = bar.baz}\"#,\n        );\n    }\n\n    #[test]\n    fn test_assign() {\n        assert_is_formatted(\n            r#\"\ngroup {title, country} (aggregate {\n  average salary,\n  average gross_salary,\n  sum salary,\n  sum gross_salary,\n  average gross_cost,\n  sum_gross_cost = sum gross_cost,\n  ct = count salary,\n})\"#,\n        );\n    }\n    #[test]\n    fn test_range() {\n        assert_is_formatted(\n            r#\"\nlet negative = -100..0\n\"#,\n        );\n\n        assert_is_formatted(\n            r#\"\nlet negative = -(100..0)\n\"#,\n        );\n\n        assert_is_formatted(\n            r#\"\nlet negative = -100..\n\"#,\n        );\n\n        assert_is_formatted(\n            r#\"\nlet negative = ..-100\n\"#,\n        );\n    }\n\n    #[test]\n    fn test_annotation() {\n        assert_is_formatted(\n            r#\"\n@deprecated\nmodule hello {\n}\n\"#,\n        );\n    }\n\n    #[test]\n    fn test_var_def() {\n        assert_is_formatted(\n            r#\"\nlet a\n\"#,\n        );\n\n        assert_is_formatted(\n            r#\"\nlet a <int>\n\"#,\n        );\n\n        assert_is_formatted(\n            r#\"\nlet a = 5\n\"#,\n        );\n\n        assert_is_formatted(\n            r#\"\n5\ninto a\n\"#,\n        );\n    }\n\n    #[test]\n    fn test_query_def() {\n        assert_is_formatted(\n            r#\"\nprql version:\"^0.9\" target:sql.sqlite\n\"#,\n        );\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/codegen/mod.rs",
    "content": "pub(crate) use ast::write_expr;\npub(crate) use types::{write_ty, write_ty_kind};\n\nmod ast;\nmod types;\n\npub trait WriteSource {\n    /// Converts self to its source representation according to specified\n    /// options.\n    ///\n    /// Returns `None` if source does not fit into [WriteOpt::rem_width].\n    fn write(&self, opt: WriteOpt) -> Option<String>;\n\n    fn write_between<S: ToString>(\n        &self,\n        prefix: S,\n        suffix: &str,\n        mut opt: WriteOpt,\n    ) -> Option<String> {\n        let mut r = String::new();\n        r += opt.consume(&prefix.to_string())?;\n        opt.context_strength = 0;\n        opt.unbound_expr = false;\n\n        let source = self.write(opt.clone())?;\n        r += opt.consume(&source)?;\n\n        r += opt.consume(suffix)?;\n        Some(r)\n    }\n\n    fn write_or_expand(&self, mut opt: WriteOpt) -> String {\n        loop {\n            if let Some(s) = self.write(opt.clone()) {\n                return s;\n            } else {\n                opt.max_width += opt.max_width / 2;\n                opt.reset_line();\n            }\n        }\n    }\n}\n\nimpl<T: WriteSource> WriteSource for &T {\n    fn write(&self, opt: WriteOpt) -> Option<String> {\n        (*self).write(opt)\n    }\n}\n\n#[derive(Clone)]\npub struct WriteOpt {\n    /// String to emit as one indentation level\n    pub tab: &'static str,\n\n    /// Maximum number of characters per line\n    pub max_width: u16,\n\n    /// Current indent used when emitting lines\n    pub indent: u16,\n\n    /// Current remaining number of characters in line\n    pub rem_width: u16,\n\n    /// Strength of the context\n    /// For top-level exprs or exprs in parenthesis, this will be 0.\n    /// For exprs in function calls, this will be 10.\n    pub context_strength: u8,\n\n    /// Position within binary operators.\n    /// Needed for omitting parenthesis in following expressions: `(a + b) + c`.\n    pub binary_position: Position,\n\n    /// True iff preceding source ends in an expression that could\n    /// be mistakenly bound into a binary op by appending an unary op.\n    ///\n    /// For example:\n    /// `join foo` has an unbound expr, since `join foo ==bar` produced a binary op.\n    pub unbound_expr: bool,\n}\n\n#[derive(Clone, PartialEq)]\npub enum Position {\n    Unspecified,\n    Left,\n    Right,\n}\n\nimpl Default for WriteOpt {\n    fn default() -> Self {\n        Self {\n            tab: \"  \",\n            max_width: 50,\n\n            indent: 0,\n            rem_width: 50,\n            context_strength: 0,\n            binary_position: Position::Unspecified,\n            unbound_expr: false,\n        }\n    }\n}\n\nimpl WriteOpt {\n    fn new_width(max_width: u16) -> Self {\n        WriteOpt {\n            max_width,\n            rem_width: max_width,\n            ..WriteOpt::default()\n        }\n    }\n\n    fn consume_width(&mut self, width: u16) -> Option<()> {\n        self.rem_width = self.rem_width.checked_sub(width)?;\n        Some(())\n    }\n\n    fn reset_line(&mut self) -> Option<()> {\n        let ident = self.tab.len() as u16 * self.indent;\n        self.rem_width = self.max_width.checked_sub(ident)?;\n        Some(())\n    }\n\n    /// Subtracts the width of the source from the remaining width and returns the source unchanged.\n    fn consume<S: AsRef<str>>(&mut self, source: S) -> Option<S> {\n        let width = if let Some(new_line) = source.as_ref().rfind('\\n') {\n            source.as_ref().len() - new_line\n        } else {\n            source.as_ref().len()\n        };\n        self.consume_width(width as u16)?;\n        Some(source)\n    }\n\n    fn write_indent(&self) -> String {\n        self.tab.repeat(self.indent as usize)\n    }\n}\n\n/// Holds a list of (generally) expressions, attempting to write them in a\n/// single line, or falling back to one-per-line\n#[derive(Debug, Clone)]\nstruct SeparatedExprs<'a, T: WriteSource> {\n    exprs: &'a [T],\n    /// The separator to use when writing the expressions on a single line; for\n    /// example `\", \"`.\n    inline: &'static str,\n    /// The separator to use when writing the expressions on separate lines, for\n    /// example `\",\"` (`/n` is implied)\n    line_end: &'static str,\n}\n\nimpl<T: WriteSource> WriteSource for SeparatedExprs<'_, T> {\n    fn write(&self, mut opt: WriteOpt) -> Option<String> {\n        // try inline\n        if let Some(inline) = self.write_inline(opt.clone()) {\n            return Some(inline);\n        }\n\n        // one per line\n        {\n            opt.indent += 1;\n\n            let mut r = String::new();\n\n            for expr in self.exprs {\n                r += \"\\n\";\n                r += &opt.write_indent();\n                opt.reset_line()?;\n                opt.rem_width.checked_sub(self.line_end.len() as u16)?;\n\n                r += &expr.write(opt.clone())?;\n                r += self.line_end;\n            }\n            opt.indent -= 1;\n            r += \"\\n\";\n            r += &opt.write_indent();\n\n            Some(r)\n        }\n    }\n}\n\nimpl<T: WriteSource> SeparatedExprs<'_, T> {\n    fn write_inline(&self, mut opt: WriteOpt) -> Option<String> {\n        let mut exprs = Vec::new();\n        for expr in self.exprs {\n            let expr = expr.write(opt.clone())?;\n\n            if expr.contains('\\n') {\n                return None;\n            }\n            opt.consume_width(expr.len() as u16)?;\n\n            exprs.push(expr);\n        }\n\n        let separators = self.inline.len() * (exprs.len().checked_sub(1).unwrap_or_default());\n        opt.consume_width(separators as u16)?;\n\n        Some(exprs.join(self.inline))\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/codegen/types.rs",
    "content": "use prqlc_parser::parser::pr;\n\nuse crate::codegen::SeparatedExprs;\n\nuse super::{WriteOpt, WriteSource};\n\npub(crate) fn write_ty(ty: &pr::Ty) -> String {\n    ty.write(WriteOpt::new_width(u16::MAX)).unwrap()\n}\n\npub(crate) fn write_ty_kind(ty: &pr::TyKind) -> String {\n    ty.write(WriteOpt::new_width(u16::MAX)).unwrap()\n}\n\nimpl WriteSource for pr::Ty {\n    fn write(&self, opt: WriteOpt) -> Option<String> {\n        if let Some(name) = &self.name {\n            Some(name.clone())\n        } else {\n            self.kind.write(opt)\n        }\n    }\n}\n\nimpl WriteSource for Option<&pr::Ty> {\n    fn write(&self, opt: WriteOpt) -> Option<String> {\n        match self {\n            Some(ty) => ty.write(opt),\n            None => Some(\"?\".to_string()),\n        }\n    }\n}\n\nimpl WriteSource for pr::TyKind {\n    fn write(&self, opt: WriteOpt) -> Option<String> {\n        use pr::TyKind::*;\n\n        match &self {\n            Ident(ident) => ident.write(opt),\n            Primitive(prim) => Some(prim.to_string()),\n            Tuple(elements) => SeparatedExprs {\n                exprs: elements,\n                inline: \", \",\n                line_end: \",\",\n            }\n            .write_between(\"{\", \"}\", opt),\n            Array(Some(elem)) => Some(format!(\"[{}]\", elem.write(opt)?)),\n            Array(None) => Some(\"[]\".into()),\n            Function(None) => Some(\"func\".to_string()),\n            Function(Some(func)) => {\n                let mut r = \"func \".to_string();\n\n                for t in &func.params {\n                    r += &t.as_ref().write(opt.clone())?;\n                    r += \" \";\n                }\n                r += \"-> \";\n                r += &func.return_ty.as_deref().write(opt)?;\n                Some(r)\n            }\n        }\n    }\n}\n\nimpl WriteSource for pr::TyTupleField {\n    fn write(&self, opt: WriteOpt) -> Option<String> {\n        match self {\n            Self::Wildcard(generic_el) => match generic_el {\n                Some(el) => Some(format!(\"{}..\", el.write(opt)?)),\n                None => Some(\"..\".to_string()),\n            },\n            Self::Single(name, expr) => {\n                let mut r = String::new();\n\n                if let Some(name) = name {\n                    r += name;\n                    r += \" = \";\n                }\n                if let Some(expr) = expr {\n                    r += &expr.write(opt)?;\n                } else {\n                    r += \"?\";\n                }\n                Some(r)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/debug/log.rs",
    "content": "//! Internal machinery for collecting and rendering debug logs.\n#![doc(hidden)]\n\nuse chrono::prelude::*;\nuse serde::Serialize;\nuse std::marker::PhantomData;\nuse std::{sync::RwLock, time::SystemTime};\nuse strum_macros::AsRefStr;\n\nuse crate::ir::{decl, pl, rq};\nuse crate::sql::pq_ast;\nuse prqlc_parser::lexer::lr;\nuse prqlc_parser::parser::pr;\n\n/// Stores debug info about current compilation.\n/// Is reset by [log_start] and [log_finish].\nstatic CURRENT_LOG: RwLock<Option<DebugLog>> = RwLock::new(None);\n\npub fn log_start() {\n    let mut lock = CURRENT_LOG.write().unwrap();\n    assert!(lock.is_none());\n\n    let started_at: DateTime<Utc> = SystemTime::now().into();\n    let started_at = format!(\"{}\", started_at.format(\"%+\"));\n\n    *lock = Some(DebugLog {\n        started_at,\n        version: crate::compiler_version().to_string(),\n        entries: Vec::new(),\n\n        suppress_count: 0,\n    });\n}\n\npub fn log_finish() -> Option<DebugLog> {\n    let mut lock = CURRENT_LOG.write().unwrap();\n    lock.take()\n}\n\n/// Will discard any new entries until the lock is dropped.\npub fn log_suppress() -> Option<LogSuppressLock> {\n    LogSuppressLock::new()\n}\n\npub fn log_stage(stage: Stage) {\n    log_entry(|| DebugEntryKind::NewStage(stage));\n}\n\npub fn log_entry(entry: impl FnOnce() -> DebugEntryKind) {\n    let mut lock: std::sync::RwLockWriteGuard<'_, Option<DebugLog>> = CURRENT_LOG.write().unwrap();\n    if let Some(log) = lock.as_mut() {\n        if log.suppress_count > 0 {\n            return;\n        }\n\n        log.entries.push(DebugEntry { kind: entry() });\n    }\n}\n\npub fn log_is_enabled() -> bool {\n    let lock: std::sync::RwLockReadGuard<'_, Option<DebugLog>> = CURRENT_LOG.read().unwrap();\n    if let Some(log) = lock.as_ref() {\n        log.suppress_count == 0\n    } else {\n        false\n    }\n}\n\n#[derive(Serialize)]\npub struct DebugLog {\n    pub(super) started_at: String,\n    pub(super) version: String,\n    pub(super) entries: Vec<DebugEntry>,\n\n    #[serde(skip)]\n    suppress_count: usize,\n}\n\n#[derive(Serialize)]\npub(super) struct DebugEntry {\n    pub(crate) kind: DebugEntryKind,\n}\n\n#[derive(Serialize, AsRefStr)]\npub enum DebugEntryKind {\n    ReprPrql(crate::SourceTree),\n    ReprLr(lr::Tokens),\n    ReprPr(pr::ModuleDef),\n    ReprPl(pl::ModuleDef),\n    ReprDecl(decl::RootModule),\n    ReprRq(rq::RelationalQuery),\n    ReprPqEarly(Vec<pq_ast::SqlTransform>),\n    ReprPq(pq_ast::SqlQuery),\n    ReprSqlParser(Box<sqlparser::ast::Query>),\n    ReprSql(String),\n\n    Message(Message),\n    NewStage(Stage),\n}\n\n#[derive(Clone, Serialize)]\npub struct Message {\n    // TODO: this is inefficient, replace with Cow<'static, str>\n    pub level: String,\n    pub file: Option<String>,\n    pub line: Option<u32>,\n    pub module_path: Option<String>,\n\n    pub text: String,\n}\n\n#[derive(Clone, Copy, Serialize, AsRefStr)]\npub enum Stage {\n    Parsing,\n    Semantic(StageSemantic),\n    Sql(StageSql),\n}\n\nimpl Stage {\n    pub(super) fn full_name(&self) -> String {\n        let stage = self.as_ref().to_lowercase();\n        let substage = self\n            .sub_stage()\n            .map(|s| \"-\".to_string() + &s.to_lowercase())\n            .unwrap_or_default();\n        format!(\"{stage}{substage}\")\n    }\n\n    pub(super) fn sub_stage(&self) -> Option<&'_ str> {\n        match self {\n            Stage::Parsing => None,\n            Stage::Semantic(s) => Some(s.as_ref()),\n            Stage::Sql(s) => Some(s.as_ref()),\n        }\n    }\n}\n\n#[derive(Clone, Copy, Serialize, AsRefStr)]\npub enum StageSemantic {\n    AstExpand,\n    Resolver,\n    Lowering,\n}\n\n#[derive(Clone, Copy, Serialize, AsRefStr)]\npub enum StageSql {\n    Anchor,\n    Postprocess,\n    Main,\n}\n\npub struct LogSuppressLock(PhantomData<usize>);\n\nimpl LogSuppressLock {\n    fn new() -> Option<Self> {\n        let mut lock = CURRENT_LOG.write().unwrap();\n        if let Some(log) = lock.as_mut() {\n            log.suppress_count += 1;\n\n            Some(LogSuppressLock(PhantomData))\n        } else {\n            None\n        }\n    }\n}\n\nimpl Drop for LogSuppressLock {\n    fn drop(&mut self) {\n        let mut lock = CURRENT_LOG.write().unwrap();\n        if let Some(log) = lock.as_mut() {\n            log.suppress_count -= 1;\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/debug/messages.rs",
    "content": "use log::{Metadata, Record};\n\nuse crate::debug;\n\npub struct MessageLogger;\n\nimpl log::Log for MessageLogger {\n    fn enabled(&self, _metadata: &Metadata) -> bool {\n        // if log is enabled, enable all message levels\n        super::log_is_enabled()\n    }\n\n    fn log(&self, record: &Record) {\n        if self.enabled(record.metadata()) {\n            super::log_entry(|| {\n                debug::DebugEntryKind::Message(debug::Message {\n                    level: record.level().to_string(),\n                    file: record.file().map(|x| x.to_string()),\n                    line: record.line(),\n                    module_path: record.module_path().map(|x| x.to_string()),\n                    text: format!(\"{}\", record.args()),\n                })\n            });\n        }\n    }\n\n    fn flush(&self) {}\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/debug/mod.rs",
    "content": "mod log;\nmod messages;\nmod render_html;\n\npub use crate::debug::log::*;\npub use messages::MessageLogger;\npub use render_html::render_log_to_html;\n"
  },
  {
    "path": "prqlc/prqlc/src/debug/render_html.rs",
    "content": "use std::collections::HashMap;\nuse std::fmt::{Debug, Result, Write};\nuse std::iter::Peekable;\n\nuse crate::sql::pq_ast;\nuse crate::{codegen, SourceTree};\n\nuse super::log::*;\nuse crate::ir::{decl, pl, rq};\nuse itertools::Itertools;\nuse prqlc_parser::lexer::lr;\nuse prqlc_parser::parser::pr;\n\npub fn render_log_to_html<W: std::io::Write>(writer: W, debug_log: &DebugLog) -> core::fmt::Result {\n    struct IoWriter<W: std::io::Write> {\n        inner: W,\n    }\n\n    impl<W: std::io::Write> core::fmt::Write for IoWriter<W> {\n        fn write_str(&mut self, s: &str) -> std::fmt::Result {\n            self.inner\n                .write(s.as_bytes())\n                .map_err(|_| std::fmt::Error)?;\n            Ok(())\n        }\n    }\n    let mut io_writer = IoWriter { inner: writer };\n\n    write_debug_log(&mut io_writer, debug_log)\n}\n\nfn write_debug_log<W: Write>(w: &mut W, debug_log: &DebugLog) -> Result {\n    writeln!(w, \"<!doctype html>\")?;\n    writeln!(w, \"<html>\")?;\n    writeln!(w, \"<head>\")?;\n    writeln!(w, r#\"<meta charset=\"utf-8\">\"#)?;\n    writeln!(w, \"<title>prqlc debug log {}</title>\", debug_log.started_at)?;\n    writeln!(w, r#\"<meta name=\"generator\" content=\"prqlc\">\"#)?;\n    writeln!(w, r#\"<meta name=\"robots\" content=\"noindex\">\"#)?;\n    writeln!(w, \"<style>{CSS_STYLES}</style>\")?;\n    writeln!(w, \"<script>{JS_SCRIPT}</script>\")?;\n    writeln!(w, \"</head>\")?;\n    writeln!(w, \"<body>\")?;\n\n    // header\n    {\n        writeln!(w, \"<header>\")?;\n        writeln!(w, \"<h1>prqlc debug log</h1>\")?;\n\n        write_key_values(\n            w,\n            &[\n                (\"started_at\", &debug_log.started_at),\n                (\"version\", &debug_log.version),\n            ],\n        )?;\n        writeln!(w, \"</header>\")?;\n    }\n\n    // pre-stage entries (this should be mostly source PRQL)\n    for (index, entry) in debug_log.entries.iter().enumerate() {\n        if let DebugEntryKind::NewStage(_) = &entry.kind {\n            break;\n        }\n\n        match &entry.kind {\n            DebugEntryKind::Message(message) => {\n                write_message(w, message)?;\n            }\n            _ => {\n                write_titled_entry(w, index, entry)?;\n            }\n        }\n    }\n\n    {\n        writeln!(w, \"<div class=message-filter>\")?;\n        for level in [\"off\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"] {\n            writeln!(\n                w,\n                r#\"<input type=\"radio\" name=\"message-filter\" id=\"msg-filter-{level}\">\"#\n            )?;\n            writeln!(w, r#\"<label for=\"msg-filter-{level}\">{level}</label>\"#)?;\n        }\n\n        writeln!(w, \"</div>\")?;\n    }\n\n    {\n        writeln!(w, \"<main class=tab-container>\")?;\n        // stage tabs\n\n        // tab handles\n        let mut entries = debug_log.entries.iter().peekable();\n        while entries.peek().is_some() {\n            write_stage_handles(w, &mut entries)?;\n        }\n\n        // tab panels\n        writeln!(w, \"<div class=tab-panels>\")?;\n        let mut entries = debug_log.entries.iter().enumerate().peekable();\n        while entries.peek().is_some() {\n            write_stage_contents(w, &mut entries)?;\n        }\n        writeln!(w, \"</div>\")?;\n\n        writeln!(w, \"</main>\")?;\n    }\n\n    writeln!(w, \"</body>\")?;\n    writeln!(w, \"</html>\")?;\n\n    Ok(())\n}\n\nfn write_stage_handles<'a, W: Write>(\n    w: &mut W,\n    entries: &mut Peekable<impl Iterator<Item = &'a DebugEntry>>,\n) -> Result {\n    // find a new stage\n    let stage = loop {\n        match entries.next() {\n            Some(entry) => {\n                if let DebugEntryKind::NewStage(s) = entry.kind {\n                    break s;\n                }\n            }\n            None => return Ok(()),\n        }\n    };\n\n    let stage_name = stage.full_name();\n\n    writeln!(\n        w,\n        r#\"<input type=\"radio\" name=\"stage-tab\" id=\"{stage_name}-tab\" aria-controls=\"{stage_name}\">\"#\n    )?;\n    writeln!(w, r#\"<label for=\"{stage_name}-tab\">{stage_name}</label>\"#)?;\n\n    Ok(())\n}\n\nfn write_stage_contents<'a, W: Write>(\n    w: &mut W,\n    entries: &mut Peekable<impl Iterator<Item = (usize, &'a DebugEntry)>>,\n) -> Result {\n    // find a new stage\n    let stage = loop {\n        match entries.next() {\n            Some((_, entry)) => {\n                if let DebugEntryKind::NewStage(s) = entry.kind {\n                    break s;\n                }\n            }\n            None => return Ok(()),\n        }\n    };\n\n    writeln!(w, \"<section id={} class=tab-panel>\", stage.full_name())?;\n    while let Some((_, entry)) = entries.peek() {\n        if matches!(entry.kind, DebugEntryKind::NewStage(_)) {\n            break;\n        }\n        let (index, entry) = entries.next().unwrap();\n\n        match &entry.kind {\n            DebugEntryKind::Message(message) => {\n                write_message(w, message)?;\n            }\n            _ => {\n                write_titled_entry(w, index, entry)?;\n            }\n        }\n    }\n    writeln!(w, \"</section>\")?;\n    Ok(())\n}\n\nfn write_message<W: Write>(w: &mut W, message: &Message) -> Result {\n    writeln!(w, r#\"<div class=\"entry msg-{}\">\"#, message.level)?;\n    write!(w, \"[<b>{}</b>\", message.level)?;\n    if let Some(module_path) = &message.module_path {\n        write!(w, r#\" {module_path}\"#)?;\n    }\n    writeln!(w, \"] {}\", message.text)?;\n    writeln!(w, \"</div>\")\n}\n\nfn write_titled_entry<W: Write>(w: &mut W, index: usize, entry: &DebugEntry) -> Result {\n    writeln!(w, r#\"<div class=entry>\"#)?;\n    let entry_id = format!(\"entry-{index}\");\n\n    writeln!(\n        w,\n        \"<input id={entry_id} class=entry-collapse type=checkbox>\"\n    )?;\n\n    {\n        writeln!(w, r#\"<label for={entry_id} class=\"entry-label clickable\">\"#)?;\n        let kind = entry.kind.as_ref()[4..].to_ascii_uppercase();\n        writeln!(\n            w,\n            r#\"[<b>REPRESENTATION</b>] <span class=\"yellow\">{kind}</span>\"#,\n        )?;\n        writeln!(w, r#\"</label>\"#)?;\n    }\n\n    {\n        writeln!(w, r#\"<div class=\"entry-content\">\"#)?;\n        match &entry.kind {\n            DebugEntryKind::ReprPrql(a) => write_repr_prql(w, a)?,\n            DebugEntryKind::ReprLr(a) => write_repr_lr(w, a)?,\n            DebugEntryKind::ReprPr(a) => write_repr_pr(w, a)?,\n            DebugEntryKind::ReprPl(a) => write_repr_pl(w, a)?,\n            DebugEntryKind::ReprDecl(a) => write_repr_decl(w, a)?,\n            DebugEntryKind::ReprRq(a) => write_repr_rq(w, a)?,\n            DebugEntryKind::ReprPqEarly(a) => write_repr_pq_early(w, a)?,\n            DebugEntryKind::ReprPq(a) => write_repr_pq(w, a)?,\n            DebugEntryKind::ReprSqlParser(a) => write_repr_sql_parser(w, a)?,\n            DebugEntryKind::ReprSql(a) => write_repr_sql(w, a)?,\n            DebugEntryKind::NewStage(_) | DebugEntryKind::Message(_) => unreachable!(),\n        }\n        writeln!(w, \"</div>\")?;\n    }\n\n    writeln!(w, \"</div>\")?;\n    Ok(())\n}\n\nfn write_repr_prql<W: Write>(w: &mut W, source_tree: &SourceTree) -> Result {\n    writeln!(w, r#\"<div class=\"prql repr\">\"#)?;\n\n    write_key_values(w, &[(\"root\", &source_tree.root)])?;\n\n    let reverse_ids: HashMap<_, _> = source_tree\n        .source_ids\n        .iter()\n        .map(|(id, path)| (path, id))\n        .collect();\n\n    for (path, source) in &source_tree.sources {\n        writeln!(w, r#\"<div class=\"source indent\">\"#)?;\n\n        let source_id = reverse_ids.get(path).unwrap();\n        write_key_values(w, &[(\"path\", &path), (\"source_id\", source_id)])?;\n\n        let source_escaped = escape_html(source);\n        writeln!(\n            w,\n            r#\"<code id=\"source-{source_id}\">{source_escaped}</code>\"#\n        )?;\n        writeln!(w, \"</div>\")?; // source\n    }\n\n    writeln!(w, \"</div>\")\n}\n\nfn write_repr_lr<W: Write>(w: &mut W, tokens: &lr::Tokens) -> Result {\n    writeln!(w, r#\"<table class=\"lr repr\">\"#)?;\n\n    for token in &tokens.0 {\n        writeln!(w, r#\"<tr class=\"token\">\"#,)?;\n        writeln!(w, \"<td>{}</td>\", escape_html(&format!(\"{:?}\", token.kind)))?;\n        writeln!(\n            w,\n            r#\"<td><span class=\"span\">{}:{}</span></td>\"#,\n            token.span.start, token.span.end\n        )?;\n        writeln!(w, \"</tr>\")?;\n    }\n\n    writeln!(w, \"</table>\")\n}\n\nfn write_repr_pr<W: Write>(w: &mut W, ast: &pr::ModuleDef) -> Result {\n    writeln!(w, r#\"<div class=\"pr repr\">\"#)?;\n\n    let json = serde_json::to_string(ast).unwrap();\n    let json_node: serde_json::Value = serde_json::from_str(&json).unwrap();\n    write_json_ast_node(w, json_node, false)?;\n\n    writeln!(w, \"</div>\")\n}\n\nfn write_repr_pl<W: Write>(w: &mut W, ast: &pl::ModuleDef) -> Result {\n    writeln!(w, r#\"<div class=\"pl repr\">\"#)?;\n\n    let json = serde_json::to_string(ast).unwrap();\n    let json_node: serde_json::Value = serde_json::from_str(&json).unwrap();\n    write_json_ast_node(w, json_node, false)?;\n\n    writeln!(w, \"</div>\")\n}\n\nfn write_repr_decl<W: Write>(w: &mut W, root_mod: &decl::RootModule) -> Result {\n    writeln!(w, r#\"<div class=\"decl repr\">\"#)?;\n\n    for (name, decl) in root_mod.module.names.iter().sorted_by_key(|x| x.0.as_str()) {\n        write_decl(w, decl, name, &root_mod.span_map)?;\n    }\n\n    writeln!(w, \"</div>\")\n}\n\nfn write_repr_rq<W: Write>(w: &mut W, ast: &rq::RelationalQuery) -> Result {\n    writeln!(w, r#\"<div class=\"rq repr\">\"#)?;\n\n    let json = serde_json::to_string(ast).unwrap();\n    let json_node: serde_json::Value = serde_json::from_str(&json).unwrap();\n    write_json_ast_node(w, json_node, false)?;\n\n    writeln!(w, \"</div>\")\n}\n\nfn write_repr_pq_early<W: Write>(w: &mut W, ast: &[pq_ast::SqlTransform]) -> Result {\n    writeln!(w, r#\"<div class=\"pq repr\">\"#)?;\n\n    let json = serde_json::to_string(ast).unwrap();\n    let json_node: serde_json::Value = serde_json::from_str(&json).unwrap();\n    write_json_ast_node(w, json_node, false)?;\n\n    writeln!(w, \"</div>\")\n}\n\nfn write_repr_pq<W: Write>(w: &mut W, ast: &pq_ast::SqlQuery) -> Result {\n    writeln!(w, r#\"<div class=\"pq repr\">\"#)?;\n\n    let json = serde_json::to_string(ast).unwrap();\n    let json_node: serde_json::Value = serde_json::from_str(&json).unwrap();\n    write_json_ast_node(w, json_node, false)?;\n\n    writeln!(w, \"</div>\")\n}\n\nfn write_repr_sql_parser<W: Write>(w: &mut W, ast: &sqlparser::ast::Query) -> Result {\n    writeln!(w, r#\"<div class=\"sql-parser repr\">\"#)?;\n\n    let json = serde_json::to_string(ast).unwrap();\n    let json_node: serde_json::Value = serde_json::from_str(&json).unwrap();\n    write_json_ast_node(w, json_node, false)?;\n\n    writeln!(w, \"</div>\")\n}\n\nfn write_repr_sql<W: Write>(w: &mut W, query: &str) -> Result {\n    writeln!(w, r#\"<div class=\"sql repr\">\"#)?;\n    writeln!(w, \"<pre><code>{query}</code></pre>\")?;\n    writeln!(w, \"</div>\")\n}\n\nfn write_key_values<W: Write>(w: &mut W, pairs: &[(&'static str, &dyn Debug)]) -> Result {\n    writeln!(w, r#\"<div class=\"key-values\">\"#)?;\n    for (k, v) in pairs {\n        writeln!(w, r#\"<div><b class=\"blue\">{k}</b>: {v:?}</div>\"#)?;\n    }\n    writeln!(w, \"</div>\")\n}\n\n/// A hacky way to reconstruct AST nodes from their JSON serialization.\n/// Finds structures that look like `{ \"BinOp\": { ... }, \"span\": ... }`\n/// and converts them to `<div class=ast-node>...</div>`.\nfn write_json_ast_node<W: Write>(\n    w: &mut W,\n    node: serde_json::Value,\n    is_node_contents: bool,\n) -> Result {\n    match node {\n        serde_json::Value::Null => write!(w, \"None\"),\n        serde_json::Value::Bool(b) => write!(w, \"{b}\"),\n        serde_json::Value::Number(n) => write!(w, \"{n}\"),\n        serde_json::Value::String(s) => write!(w, \"{s}\"),\n        serde_json::Value::Array(items) => {\n            writeln!(w, r#\"<ul class=\"json-array\">\"#)?;\n            for item in items {\n                write!(w, \"<li>\")?;\n                write_json_ast_node(w, item, false)?;\n                write!(w, \"</li>\")?;\n            }\n            writeln!(w, \"</ul>\")?;\n            Ok(())\n        }\n        serde_json::Value::Object(properties) => {\n            let is_ast_node = properties.contains_key(\"span\")\n                || properties.contains_key(\"id\")\n                || (properties.len() == 1\n                    && !is_node_contents\n                    && properties.values().next().unwrap().is_object());\n            if is_ast_node {\n                return write_ast_node_from_object(w, properties);\n            }\n\n            writeln!(w, r#\"<div class=\"json-object\">\"#)?;\n            for (key, value) in properties {\n                if key == \"ty\" || key == \"return_ty\" {\n                    // special case for better type printing\n                    let ty_json = value.to_string();\n                    if let Ok(ty) = serde_json::from_str::<pr::Ty>(&ty_json) {\n                        let ty_prql = escape_html(&codegen::write_ty(&ty));\n                        write!(w, r#\"<span>{key}: {ty_prql}</span>\"#)?;\n                    }\n                    continue;\n                }\n\n                write!(w, r#\"<span>{key}: </span><div class=\"json-value\">\"#)?;\n                write_json_ast_node(w, value, false)?;\n                writeln!(w, \"</div>\")?;\n            }\n            writeln!(w, \"</div>\")\n        }\n    }\n}\n\nfn write_ast_node_from_object<W: Write>(\n    w: &mut W,\n    mut properties: serde_json::Map<String, serde_json::Value>,\n) -> Result {\n    let id: Option<i64> = properties.remove(\"id\").and_then(|s| match s {\n        serde_json::Value::Null => None,\n        serde_json::Value::Number(n) if n.is_i64() => n.as_i64(),\n        _ => unreachable!(\"expected id to be int, got: {}\", s),\n    });\n    let span: Option<String> = properties.remove(\"span\").and_then(|s| match s {\n        serde_json::Value::Null => None,\n        serde_json::Value::String(s) => Some(s),\n        _ => None,\n        // We previously used to error, but sqlparser now has a type that\n        // doesn't include spans, so we just ignore it.\n        // _ => unreachable!(\"expected span to be string, got: {}\", s),\n    });\n    let ty: Option<serde_json::Value> = properties.remove(\"ty\");\n\n    let first_item = properties.into_iter().next();\n    let (name, contents) =\n        first_item.unwrap_or_else(|| (\"<empty>\".into(), serde_json::Value::Null));\n\n    write!(w, r#\"<details class=ast-node open tabindex=2>\"#)?;\n\n    {\n        write!(w, \"<summary class=header>\")?;\n\n        let h2_id = id.map(|i| format!(\"id=ast-{i} \")).unwrap_or_default();\n        write!(w, \"<h2 {h2_id}class=clickable>{name}</h2>\")?;\n\n        if let Some(id) = id {\n            write!(w, r#\"<span>id={id}</span>\"#)?;\n        }\n        if let Some(span) = span {\n            write!(w, r#\"<span class=\"span\">{span}</span>\"#)?;\n        }\n        if let Some(ty) = ty {\n            let ty_json = ty.to_string();\n            if let Ok(ty) = serde_json::from_str::<pr::Ty>(&ty_json) {\n                let ty_prql = codegen::write_ty(&ty);\n                write!(w, r#\"<span class=\"ty\">{ty_prql}</span>\"#)?;\n            }\n        }\n        write!(w, \"</summary>\")?;\n    }\n\n    {\n        write!(w, r#\"<content class=\"contents indent\">\"#)?;\n        write_json_ast_node(w, contents, true)?;\n        write!(w, \"</content>\")?;\n    }\n\n    write!(w, \"</details>\")\n}\n\nfn write_decl<W: Write>(\n    w: &mut W,\n    decl: &decl::Decl,\n    name: &String,\n    span_map: &HashMap<usize, pr::Span>,\n) -> Result {\n    let open = if name != \"std\" { \" open\" } else { \"\" };\n    write!(w, r#\"<details class=\"ast-node\" {open} tabindex=2>\"#)?;\n\n    // header\n    {\n        write!(w, \"<summary class=header>\")?;\n        write!(w, r#\"<h2 class=\"clickable blue\">{name}</h2>\"#)?;\n\n        let span = decl.declared_at.as_ref().and_then(|id| span_map.get(id));\n        if let Some(span) = span {\n            write!(w, r#\"<span class=\"span\">{span:?}</span>\"#)?;\n        }\n        write!(w, \"</summary>\")?; // header\n    }\n\n    {\n        write!(w, r#\"<content class=\"contents indent\">\"#)?;\n        match &decl.kind {\n            decl::DeclKind::Module(m) => {\n                for (name, decl) in m.names.iter().sorted_by_key(|x| x.0.as_str()) {\n                    write_decl(w, decl, name, span_map)?;\n                }\n            }\n            decl::DeclKind::Expr(expr) => {\n                let json = serde_json::to_string(expr).unwrap();\n                let json_node: serde_json::Value = serde_json::from_str(&json).unwrap();\n                write_json_ast_node(w, json_node, false)?;\n            }\n            decl::DeclKind::Ty(ty) => {\n                writeln!(w, r#\"<span>{}</span>\"#, escape_html(&codegen::write_ty(ty)))?;\n            }\n            decl::DeclKind::TableDecl(table_decl) => {\n                let json = serde_json::to_string(table_decl).unwrap();\n                let json_node: serde_json::Value = serde_json::from_str(&json).unwrap();\n                write_json_ast_node(w, json_node, false)?;\n            }\n            _ => {\n                write!(w, r#\"<div>{}</div>\"#, decl.kind)?;\n            }\n        }\n        write!(w, \"</content>\")?;\n    }\n\n    write!(w, \"</details>\")\n}\n\nfn escape_html(text: &str) -> String {\n    text.replace('&', \"&amp;\")\n        .replace('<', \"&lt;\")\n        .replace('>', \"&gt;\")\n        .replace('\"', \"&quot;\")\n        .replace('\\'', \"&#039;\")\n}\n\nconst CSS_STYLES: &str = r#\"\nbody {\n    font-size: 12px;\n    font-family: monospace;\n\n    --background: #1F1F1F;\n    --background-hover: #4D4F51;\n    --background-focus: #623315;\n    --text: #CACACA;\n    --text-blue: #4ebffc;\n    --text-green: #4BBFA7;\n    --text-yellow: #DCDCAA;\n    --text-muted: gray;\n\n    background-color: var(--background);\n    color: var(--text);\n}\n\nsummary::marker {\n    content: \"\";\n}\n\n.clickable {\n    cursor: pointer;\n    text-decoration: underline;\n}\n\n.highlight-hover {\n    background-color: var(--background-hover);\n}\n.highlight-focus {\n    background-color: var(--background-focus);\n}\n.yellow {\n    color: var(--text-yellow);\n}\n.blue:not(#fakeId) {\n    color: var(--text-blue);\n}\n.muted {\n    color: var(--text-muted);\n}\n\n.key-values {\n    display: flex;\n    gap: 1em;\n}\n\n.tab-container > .tab-panels > .tab-panel {\n    display: none;\n}\n.tab-container > input:first-child:checked ~ .tab-panels > .tab-panel:first-child,\n.tab-container > input:nth-child(3):checked ~ .tab-panels > .tab-panel:nth-child(2),\n.tab-container > input:nth-child(5):checked ~ .tab-panels > .tab-panel:nth-child(3),\n.tab-container > input:nth-child(7):checked ~ .tab-panels > .tab-panel:nth-child(4),\n.tab-container > input:nth-child(9):checked ~ .tab-panels > .tab-panel:nth-child(5),\n.tab-container > input:nth-child(11):checked ~ .tab-panels > .tab-panel:nth-child(6),\n.tab-container > input:nth-child(13):checked ~ .tab-panels > .tab-panel:nth-child(7),\n.tab-container > input:nth-child(15):checked ~ .tab-panels > .tab-panel:nth-child(8),\n.tab-container > input:nth-child(17):checked ~ .tab-panels > .tab-panel:nth-child(9) {\n    display: block;\n}\n.tab-container > input {\n    display: none;\n}\n.tab-container > label {\n    display: inline-block;\n    padding: 1em;\n    cursor: pointer;\n    border: 1px solid transparent;\n    border-bottom: 0;\n}\n.tab-container > input:checked + label {\n    border-color: var(--text);\n    border-bottom: 1px solid var(--background);\n    margin-bottom: -1px;\n}\n\nsection.tab-panel {\n    border-top: 1px solid;\n    padding-top: 1rem;\n}\n\n.entry {\n    &>.entry-label {\n        margin: 0;\n        display: block;\n    }\n    &>.entry-collapse {\n        display: none;\n    }\n    &>.entry-collapse:checked + .entry-label + .entry-content {\n        display: none;\n    }\n    &>.entry-content {\n        display: flex;\n        flex-direction: column;\n    }\n}\n\n.msg-ERROR, .msg-WARN, .msg-INFO, .msg-DEBUG, .msg-TRACE {\n    display: none;\n}\n.msg-filter-error .msg-ERROR {\n    display: block;\n}\n.msg-filter-warn .msg-WARN,\n.msg-filter-warn .msg-ERROR {\n    display: block;\n}\n.msg-filter-info .msg-INFO,\n.msg-filter-info .msg-WARN,\n.msg-filter-info .msg-ERROR {\n    display: block;\n}\n.msg-filter-debug .msg-DEBUG,\n.msg-filter-debug .msg-INFO,\n.msg-filter-debug .msg-WARN,\n.msg-filter-debug .msg-ERROR {\n    display: block;\n}\n.msg-filter-trace .msg-TRACE,\n.msg-filter-trace .msg-DEBUG,\n.msg-filter-trace .msg-INFO,\n.msg-filter-trace .msg-WARN,\n.msg-filter-trace .msg-ERROR {\n    display: block;\n}\n\ncode {\n    white-space: preserve;\n    word-break: keep-all;\n}\n.indent {\n    padding-left: 1em;\n    margin-top: 2px;\n    border-left: 1px solid gray;\n}\n.span {\n    color: var(--text-muted);\n}\ntable.repr.lr {\n    border-collapse: collapse;\n}\n\n.ast-node>.header {\n    display: flex;\n}\n.ast-node>.contents {\n    display: block;\n}\n.ast-node>.header>h2 {\n    font-size: inherit;\n    color: var(--text-green);\n    margin: 0;\n}\n.ast-node>.header>span {\n    display: inline-block;\n    margin-left: 1em;\n}\n.ast-node:not([open])>.header::after {\n    content: '...';\n    margin-left: 1em;\n}\n.ast-node>.contents.indent>.json-array,\n.ast-node>.contents.indent>.json-object {\n    padding-left: 0;\n}\n.ast-node:focus {\n    background-color: var(--background-focus);\n}\n\n.json-array {\n    margin: 0;\n    list-style-type: \"- \";\n}\n.json-array,.json-object,.json-value {\n    padding-left: 1em;\n}\n\"#;\n\nconst JS_SCRIPT: &str = r#\"\nconst ast_node_mousedown = (event) => {\n    event.stopPropagation();\n\n    const ast_node = event.currentTarget;\n\n    if (document.activeElement == ast_node) {\n        // unfocus after click (and focusing) is finished\n        setTimeout(() => {\n            ast_node.blur();\n            highlight(null, null, 'highlight-focus');\n        }, 0)\n    }\n};\n\nconst ast_node_focus = (event) => {\n    event.stopPropagation();\n\n    const ast_node = event.currentTarget;\n\n    const span_element = ast_node.querySelector(':scope > .header > .span');\n    highlight(span_element, null, 'highlight-focus');\n};\n\nconst ast_node_mouseover = (event) => {\n    if (document.activeElement != document.body) {\n        // something else has focus, we don't show hover highlight\n        event.stopPropagation();\n        return;\n    }\n\n    const ast_node = event.currentTarget;\n    if (ast_node.classList.contains(\"highlight-hover\")) {\n        event.stopPropagation();\n        return;\n    }\n\n    // find the span DOM node\n    const span_element = ast_node.querySelector(':scope > .header > .span');\n    if (!span_element) {\n        // if there is no node, return without stopping propagation\n        return;\n    }\n\n    event.stopPropagation();\n    highlight(span_element, ast_node, 'highlight-hover');\n}\n\nconst highlight = (span_element, origin_element, highlight_class) => {\n    // remove all existing highlights\n    document.querySelectorAll(\".\" + highlight_class).forEach(e => {\n        e.classList.remove(highlight_class);\n    });\n\n    // highlight origin element\n    if (origin_element) {\n        origin_element.classList.add(highlight_class);\n    }\n\n    // highlight source\n    if (span_element) {\n        const span = extract_span(span_element);\n        const code_element = document.getElementById(`source-${span.source_id}`);\n        if (!code_element) {\n            console.error(`cannot find source with id=${span.source_id}`);\n        } else {\n            const source_text = code_element.innerText;\n\n            const before = escape_html(source_text.substring(0, span.start));\n            const selected = escape_html(source_text.substring(span.start, span.end));\n            const after = escape_html(source_text.substring(span.end));\n            code_element.innerHTML = `${before}<span class=${highlight_class}>${selected}</span>${after}`;\n        }\n    }\n};\n\nconst escape_html = (text) => {\n    return text\n        .replace(/&/g, \"&amp;\")\n        .replace(/</g, \"&lt;\")\n        .replace(/>/g, \"&gt;\")\n        .replace(/\"/g, \"&quot;\")\n        .replace(/'/g, \"&#039;\");\n}\n\nconst extract_span = (span_element) => {\n    const parts = span_element.innerText.split(':');\n    const source_id = Number.parseInt(parts[0]);\n    const start_end = parts[1].split('-');\n    const start = Number.parseInt(start_end[0]);\n    const end = Number.parseInt(start_end[1]);\n    return { source_id, start, end };\n};\n\nconst message_filter_change = (event) => {\n    if (!event.target.checked) {\n        return;\n    }\n    const active_id = document.querySelector(\".message-filter > input:checked\").id;\n\n    const main = document.getElementsByTagName('main')[0];\n    main.classList.forEach(c => {\n        if (c.startsWith(\"msg-filter-\")) {\n            main.classList.remove(c);\n        }\n    });\n    main.classList.add(active_id);\n};\n\ndocument.addEventListener('DOMContentLoaded', () => {\n    const ast_nodes = document.querySelectorAll(\".ast-node\");\n    ast_nodes.forEach(ast_node => {\n        ast_node.addEventListener(\"mouseover\", ast_node_mouseover);\n        ast_node.addEventListener(\"mousedown\", ast_node_mousedown);\n        ast_node.addEventListener(\"focus\", ast_node_focus);\n    });\n\n    const message_filter_nodes = document.querySelectorAll(\".message-filter > input\");\n    message_filter_nodes.forEach(node => {\n        node.addEventListener(\"change\", message_filter_change);\n    });\n});\n\"#;\n"
  },
  {
    "path": "prqlc/prqlc/src/error_message.rs",
    "content": "use std::collections::HashMap;\nuse std::error::Error as StdError;\nuse std::fmt::{self, Debug, Display, Formatter};\nuse std::ops::Range;\nuse std::path::PathBuf;\n\nuse ariadne::{Cache, Config, Label, Report, ReportKind, Source};\nuse serde::Serialize;\n\nuse crate::Span;\nuse crate::{Error, Errors, MessageKind, SourceTree};\n\n#[derive(Clone, Serialize)]\npub struct ErrorMessage {\n    /// Message kind. Currently only Error is implemented.\n    pub kind: MessageKind,\n    /// Machine-readable identifier of the error\n    pub code: Option<String>,\n    /// Plain text of the error\n    pub reason: String,\n    /// A list of suggestions of how to fix the error\n    pub hints: Vec<String>,\n    /// Character offset of error origin within a source file\n    pub span: Option<Span>,\n    /// Annotated code, containing cause and hints.\n    pub display: Option<String>,\n    /// Line and column number of error origin within a source file\n    pub location: Option<SourceLocation>,\n}\n\n/// Location within the source file.\n/// Tuples contain:\n/// - line number (0-based),\n/// - column number within that line (0-based),\n#[derive(Debug, Clone, Serialize)]\npub struct SourceLocation {\n    pub start: (usize, usize),\n\n    pub end: (usize, usize),\n}\n\nimpl Display for ErrorMessage {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        // https://github.com/zesterer/ariadne/issues/52\n        if let Some(display) = &self.display {\n            let message_without_trailing_spaces = display\n                .split('\\n')\n                .map(str::trim_end)\n                .collect::<Vec<_>>()\n                .join(\"\\n\");\n            f.write_str(&message_without_trailing_spaces)?;\n        } else {\n            let code = (self.code.as_ref())\n                .map(|c| format!(\"[{c}] \"))\n                .unwrap_or_default();\n\n            writeln!(f, \"{}Error: {}\", code, &self.reason)?;\n            for hint in &self.hints {\n                // TODO: consider alternative formatting for hints.\n                writeln!(f, \"↳ Hint: {hint}\")?;\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl Debug for ErrorMessage {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        Display::fmt(&self, f)\n    }\n}\n\nimpl From<Error> for ErrorMessage {\n    fn from(e: Error) -> Self {\n        log::debug!(\"{e:#?}\");\n        ErrorMessage {\n            code: e.code.map(str::to_string),\n            kind: e.kind,\n            reason: e.reason.to_string(),\n            hints: e.hints,\n            span: e.span,\n            display: None,\n            location: None,\n        }\n    }\n}\n\nimpl From<Vec<ErrorMessage>> for ErrorMessages {\n    fn from(errors: Vec<ErrorMessage>) -> Self {\n        ErrorMessages { inner: errors }\n    }\n}\n\n#[derive(Debug, Clone, Serialize)]\npub struct ErrorMessages {\n    pub inner: Vec<ErrorMessage>,\n}\nimpl StdError for ErrorMessages {}\n\nimpl From<ErrorMessage> for ErrorMessages {\n    fn from(e: ErrorMessage) -> Self {\n        ErrorMessages { inner: vec![e] }\n    }\n}\n\nimpl From<Error> for ErrorMessages {\n    fn from(e: Error) -> Self {\n        ErrorMessages {\n            inner: vec![ErrorMessage::from(e)],\n        }\n    }\n}\n\nimpl From<Errors> for ErrorMessages {\n    fn from(errs: Errors) -> Self {\n        ErrorMessages {\n            inner: errs.0.into_iter().map(ErrorMessage::from).collect(),\n        }\n    }\n}\n\nimpl Display for ErrorMessages {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        for e in &self.inner {\n            Display::fmt(&e, f)?;\n        }\n        Ok(())\n    }\n}\n\nimpl ErrorMessages {\n    pub fn to_json(&self) -> String {\n        serde_json::to_string(self).unwrap()\n    }\n\n    /// Computes message location and builds the pretty display.\n    pub fn composed(mut self, sources: &SourceTree) -> Self {\n        let mut cache = FileTreeCache::new(sources);\n\n        for e in &mut self.inner {\n            let Some(span) = e.span else {\n                continue;\n            };\n            let Some(source_path) = sources.source_ids.get(&span.source_id) else {\n                continue;\n            };\n\n            let Ok(source) = cache.fetch(source_path) else {\n                continue;\n            };\n            e.location = e.compose_location(source);\n\n            assert!(\n                e.location.is_some(),\n                \"span {:?} is out of bounds of the source (len = {})\",\n                e.span,\n                source.len()\n            );\n            e.display = e.compose_display(source_path.clone(), &mut cache);\n        }\n        self\n    }\n}\n\nimpl ErrorMessage {\n    fn compose_display(&self, source_path: PathBuf, cache: &mut FileTreeCache) -> Option<String> {\n        // We always pass color to ariadne as true, and then (currently) strip later.\n        let config = Config::default().with_color(true);\n\n        // Create a span tuple with the source path and the error range\n        let span = Range::from(self.span?);\n        let error_span = (source_path.clone(), span.start..span.end);\n\n        let mut report = Report::build(ReportKind::Error, error_span.clone())\n            .with_config(config)\n            .with_label(Label::new(error_span).with_message(&self.reason));\n\n        if let Some(code) = &self.code {\n            report = report.with_code(code);\n        }\n\n        // I don't know how to set multiple hints...\n        if !self.hints.is_empty() {\n            report.set_help(&self.hints[0]);\n        }\n        if self.hints.len() > 1 {\n            report.set_note(&self.hints[1]);\n        }\n        if self.hints.len() > 2 {\n            report.set_message(&self.hints[2]);\n        }\n\n        let mut out = Vec::new();\n        report.finish().write(cache, &mut out).ok()?;\n        String::from_utf8(out)\n            .ok()\n            .map(|x| crate::utils::maybe_strip_colors(x.as_str()))\n    }\n\n    fn compose_location(&self, source: &Source) -> Option<SourceLocation> {\n        let span = self.span?;\n\n        let start = source.get_offset_line(span.start)?;\n        let end = source.get_offset_line(span.end)?;\n        Some(SourceLocation {\n            start: (start.1, start.2),\n            end: (end.1, end.2),\n        })\n    }\n}\n\nstruct FileTreeCache<'a> {\n    file_tree: &'a SourceTree,\n    cache: HashMap<PathBuf, Source>,\n}\nimpl<'a> FileTreeCache<'a> {\n    fn new(file_tree: &'a SourceTree) -> Self {\n        FileTreeCache {\n            file_tree,\n            cache: HashMap::new(),\n        }\n    }\n}\n\nimpl Cache<PathBuf> for FileTreeCache<'_> {\n    type Storage = String;\n    fn fetch(&mut self, id: &PathBuf) -> Result<&Source<Self::Storage>, impl fmt::Debug> {\n        let file_contents = match self.file_tree.sources.get(id) {\n            Some(v) => v,\n            None => return Err(format!(\"Unknown file `{id:?}`\")),\n        };\n\n        Ok(self\n            .cache\n            .entry(id.clone())\n            .or_insert_with(|| Source::from(file_contents.to_string())))\n    }\n\n    fn display<'b>(&self, id: &'b PathBuf) -> Option<impl fmt::Display + 'b> {\n        id.as_os_str().to_str().map(str::to_string)\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/decl.rs",
    "content": "use std::collections::HashMap;\nuse std::fmt::Debug;\n\nuse enum_as_inner::EnumAsInner;\nuse itertools::Itertools;\nuse serde::{Deserialize, Serialize};\n\nuse crate::codegen::write_ty;\nuse crate::ir::pl;\nuse crate::pr::{Span, Ty};\nuse crate::semantic::write_pl;\n\n/// Context of the pipeline.\n#[derive(Default, Serialize, Deserialize, Clone)]\npub struct RootModule {\n    /// Map of all accessible names (for each namespace)\n    pub module: Module,\n\n    pub span_map: HashMap<usize, Span>,\n}\n\n#[derive(Default, PartialEq, Serialize, Deserialize, Clone)]\npub struct Module {\n    /// Names declared in this module. This is the important thing.\n    pub names: HashMap<String, Decl>,\n\n    /// List of relative paths to include in search path when doing lookup in\n    /// this module.\n    ///\n    /// Assuming we want to lookup `average`, which is in `std`. The root module\n    /// does not contain the `average`. So instead:\n    /// - look for `average` in root module and find nothing,\n    /// - follow redirects in root module,\n    /// - because of redirect `std`, so we look for `average` in `std`,\n    /// - there is `average` is `std`,\n    /// - result of the lookup is FQ ident `std.average`.\n    pub redirects: Vec<pl::Ident>,\n\n    /// A declaration that has been shadowed (overwritten) by this module.\n    pub shadowed: Option<Box<Decl>>,\n}\n\n/// A struct containing information about a single declaration.\n#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)]\npub struct Decl {\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub declared_at: Option<usize>,\n\n    pub kind: DeclKind,\n\n    /// Some declarations (like relation columns) have an order to them.\n    /// 0 means that the order is irrelevant.\n    #[serde(skip_serializing_if = \"is_zero\")]\n    pub order: usize,\n\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub annotations: Vec<pl::Annotation>,\n}\n\n/// The Declaration itself.\n#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, EnumAsInner)]\npub enum DeclKind {\n    /// A nested namespace\n    Module(Module),\n\n    /// Nested namespaces that do lookup in layers from top to bottom, stopping at first match.\n    LayeredModules(Vec<Module>),\n\n    TableDecl(TableDecl),\n\n    InstanceOf(pl::Ident, Option<Ty>),\n\n    /// A single column. Contains id of target which is either:\n    /// - an input relation that is source of this column or\n    /// - a column expression.\n    Column(usize),\n\n    /// Contains a default value to be created in parent namespace when NS_INFER is matched.\n    Infer(Box<DeclKind>),\n\n    Expr(Box<pl::Expr>),\n\n    Ty(Ty),\n\n    QueryDef(pl::QueryDef),\n\n    /// Equivalent to the declaration pointed to by the fully qualified ident\n    Import(pl::Ident),\n}\n\n#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]\npub struct TableDecl {\n    /// This will always be `TyKind::Array(TyKind::Tuple)`.\n    /// It is being preparing to be merged with [DeclKind::Expr].\n    /// It used to keep track of columns.\n    pub ty: Option<Ty>,\n\n    pub expr: TableExpr,\n}\n\n#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, EnumAsInner)]\npub enum TableExpr {\n    /// In SQL, this is a CTE\n    RelationVar(Box<pl::Expr>),\n\n    /// Actual table in a database. In SQL it can be referred to by name.\n    LocalTable,\n\n    /// No expression (this decl just tracks a relation literal).\n    None,\n\n    /// A placeholder for a relation that will be provided later.\n    Param(String),\n}\n\n#[derive(Clone, Eq, Debug, PartialEq, Serialize, Deserialize)]\npub enum TableColumn {\n    Wildcard,\n    Single(Option<String>),\n}\n\nimpl std::fmt::Debug for RootModule {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.module.fmt(f)\n    }\n}\n\nimpl std::fmt::Debug for Module {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut ds = f.debug_struct(\"Module\");\n\n        if !self.redirects.is_empty() {\n            let redirects = self.redirects.iter().map(|x| x.to_string()).collect_vec();\n            ds.field(\"redirects\", &redirects);\n        }\n\n        if self.names.len() < 15 {\n            ds.field(\"names\", &DebugNames(&self.names));\n        } else {\n            ds.field(\"names\", &format!(\"... {} entries ...\", self.names.len()));\n        }\n        if self.shadowed.is_some() {\n            ds.field(\"shadowed\", &\"(hidden)\");\n        }\n        ds.finish()\n    }\n}\n\nstruct DebugNames<'a>(&'a HashMap<String, Decl>);\n\nimpl std::fmt::Debug for DebugNames<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut dm = f.debug_map();\n        for (n, decl) in self.0.iter().sorted_by_key(|x| x.0) {\n            dm.entry(n, decl);\n        }\n        dm.finish()\n    }\n}\n\nimpl Default for DeclKind {\n    fn default() -> Self {\n        DeclKind::Module(Module::default())\n    }\n}\n\nimpl From<DeclKind> for Decl {\n    fn from(kind: DeclKind) -> Self {\n        Decl {\n            kind,\n            declared_at: None,\n            order: 0,\n            annotations: Vec::new(),\n        }\n    }\n}\n\nimpl std::fmt::Display for Decl {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        std::fmt::Display::fmt(&self.kind, f)\n    }\n}\n\nimpl std::fmt::Display for DeclKind {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Module(arg0) => f.debug_tuple(\"Module\").field(arg0).finish(),\n            Self::LayeredModules(arg0) => f.debug_tuple(\"LayeredModules\").field(arg0).finish(),\n            Self::TableDecl(TableDecl { ty, expr }) => {\n                write!(\n                    f,\n                    \"TableDecl: {} {expr:?}\",\n                    ty.as_ref().map(write_ty).unwrap_or_default()\n                )\n            }\n            Self::InstanceOf(arg0, _) => write!(f, \"InstanceOf: {arg0}\"),\n            Self::Column(arg0) => write!(f, \"Column (target {arg0})\"),\n            Self::Infer(arg0) => write!(f, \"Infer (default: {arg0})\"),\n            Self::Expr(arg0) => write!(f, \"Expr: {}\", write_pl(*arg0.clone())),\n            Self::Ty(arg0) => write!(f, \"Ty: {}\", write_ty(arg0)),\n            Self::QueryDef(_) => write!(f, \"QueryDef\"),\n            Self::Import(arg0) => write!(f, \"Import {arg0}\"),\n        }\n    }\n}\n\nfn is_zero(x: &usize) -> bool {\n    *x == 0\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/generic.rs",
    "content": "use schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse prqlc_parser::generic;\n\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]\npub struct ColumnSort<T> {\n    pub direction: SortDirection,\n    pub column: T,\n}\n\n#[derive(Debug, Clone, Serialize, Default, Deserialize, PartialEq, Eq, JsonSchema)]\npub enum SortDirection {\n    #[default]\n    Asc,\n    Desc,\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct WindowFrame<T> {\n    pub kind: WindowKind,\n    pub range: generic::Range<T>,\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]\npub enum WindowKind {\n    Rows,\n    Range,\n}\n\nimpl<T> WindowFrame<T> {\n    pub(crate) fn is_default(&self) -> bool {\n        matches!(\n            self,\n            WindowFrame {\n                kind: WindowKind::Rows,\n                range: generic::Range {\n                    start: None,\n                    end: None\n                }\n            }\n        )\n    }\n}\n\nimpl<T> Default for WindowFrame<T> {\n    fn default() -> Self {\n        Self {\n            kind: WindowKind::Rows,\n            range: generic::Range::unbounded(),\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/mod.rs",
    "content": "//! Intermediate Representations of Abstract Syntax Tree\n//!\npub use prqlc_parser::span::Span;\n\npub mod decl;\npub mod generic;\npub mod pl;\npub mod rq;\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/pl/expr.rs",
    "content": "use std::collections::HashMap;\n\nuse enum_as_inner::EnumAsInner;\nuse prqlc_parser::generic;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse super::{Lineage, TransformCall};\nuse crate::codegen::write_ty;\nuse crate::pr::{Ident, Literal, Span, Ty};\n\n/// Expr is anything that has a value and thus a type.\n/// Most of these can contain other [Expr] themselves; literals should be [ExprKind::Literal].\n#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct Expr {\n    #[serde(flatten)]\n    pub kind: ExprKind,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub span: Option<Span>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub alias: Option<String>,\n\n    /// Unique identifier of the node. Set exactly once during semantic::resolve.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub id: Option<usize>,\n\n    /// For [Ident]s, this is id of node referenced by the ident\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub target_id: Option<usize>,\n\n    /// Type of expression this node represents.\n    /// [None] means that type should be inferred.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub ty: Option<Ty>,\n\n    /// Information about where data of this expression will come from.\n    ///\n    /// Currently, this is used to infer relational pipeline frames.\n    /// Must always exists if ty is a relation.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub lineage: Option<Lineage>,\n\n    #[serde(skip)]\n    pub needs_window: bool,\n\n    /// When true on [ExprKind::Tuple], this list will be flattened when placed\n    /// in some other list.\n    // TODO: maybe we should have a special ExprKind instead of this flag?\n    #[serde(skip)]\n    pub flatten: bool,\n}\n\n#[derive(\n    Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize, strum::AsRefStr, JsonSchema,\n)]\npub enum ExprKind {\n    Ident(Ident),\n    All {\n        within: Box<Expr>,\n        except: Box<Expr>,\n    },\n    Literal(Literal),\n\n    Tuple(Vec<Expr>),\n    Array(Vec<Expr>),\n    FuncCall(FuncCall),\n    Func(Box<Func>),\n    TransformCall(TransformCall),\n    SString(Vec<InterpolateItem>),\n    FString(Vec<InterpolateItem>),\n    Case(Vec<SwitchCase>),\n    RqOperator {\n        name: String,\n        args: Vec<Expr>,\n    },\n\n    /// placeholder for values provided after query is compiled\n    Param(String),\n\n    /// When used instead of function body, the function will be translated to a RQ operator.\n    /// Contains ident of the RQ operator.\n    Internal(String),\n}\n\n/// Function call.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct FuncCall {\n    pub name: Box<Expr>,\n    pub args: Vec<Expr>,\n    #[serde(default, skip_serializing_if = \"HashMap::is_empty\")]\n    pub named_args: HashMap<String, Expr>,\n}\n\n/// Function called with possibly missing positional arguments.\n/// May also contain environment that is needed to evaluate the body.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct Func {\n    /// Name of the function. Used for user-facing messages only.\n    pub name_hint: Option<Ident>,\n\n    /// Type requirement for the function body expression.\n    pub return_ty: Option<Ty>,\n\n    /// Expression containing parameter (and environment) references.\n    pub body: Box<Expr>,\n\n    /// Positional function parameters.\n    pub params: Vec<FuncParam>,\n\n    /// Named function parameters.\n    pub named_params: Vec<FuncParam>,\n\n    /// Arguments that have already been provided.\n    pub args: Vec<Expr>,\n\n    /// Additional variables that the body of the function may need to be\n    /// evaluated.\n    pub env: HashMap<String, Expr>,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct FuncParam {\n    pub name: String,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub ty: Option<Ty>,\n\n    pub default_value: Option<Box<Expr>>,\n}\n\npub type Range = generic::Range<Box<Expr>>;\npub type InterpolateItem = generic::InterpolateItem<Expr>;\npub type SwitchCase = generic::SwitchCase<Box<Expr>>;\n\nimpl From<Literal> for ExprKind {\n    fn from(value: Literal) -> Self {\n        ExprKind::Literal(value)\n    }\n}\n\nimpl From<Ident> for ExprKind {\n    fn from(value: Ident) -> Self {\n        ExprKind::Ident(value)\n    }\n}\n\nimpl From<Func> for ExprKind {\n    fn from(value: Func) -> Self {\n        ExprKind::Func(Box::new(value))\n    }\n}\n\nimpl std::fmt::Debug for Expr {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut ds = f.debug_struct(\"Expr\");\n\n        if let Some(x) = &self.span {\n            ds.field(\"span\", x);\n        }\n        ds.field(\"kind\", &self.kind);\n        if let Some(x) = &self.alias {\n            ds.field(\"alias\", x);\n        }\n        if let Some(x) = &self.id {\n            ds.field(\"id\", x);\n        }\n        if let Some(x) = &self.target_id {\n            ds.field(\"target_id\", x);\n        }\n        if self.needs_window {\n            ds.field(\"needs_window\", &self.needs_window);\n        }\n        if self.flatten {\n            ds.field(\"flatten\", &self.flatten);\n        }\n        if let Some(x) = &self.ty {\n            // DebugTy is needed to get around string quotes that\n            // would be printed if we Debug-ed the string directly.\n            struct DebugTy<'a>(&'a Ty);\n            impl std::fmt::Debug for DebugTy<'_> {\n                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                    f.write_str(&write_ty(self.0))\n                }\n            }\n            ds.field(\"ty\", &DebugTy(x));\n        }\n        if let Some(x) = &self.lineage {\n            ds.field(\"lineage\", x);\n        }\n        ds.finish()\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/pl/extra.rs",
    "content": "use enum_as_inner::EnumAsInner;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse crate::ir::generic::WindowKind;\nuse crate::ir::pl::{Expr, ExprKind, Func, FuncCall, Ident, Range};\nuse crate::pr::Ty;\n\nimpl FuncCall {\n    pub fn new_simple(name: Expr, args: Vec<Expr>) -> Self {\n        FuncCall {\n            name: Box::new(name),\n            args,\n            named_args: Default::default(),\n        }\n    }\n}\n\n/// An expression that may have already been converted to a type.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, EnumAsInner, JsonSchema)]\npub enum TyOrExpr {\n    Ty(Ty),\n    Expr(Box<Expr>),\n}\n\nimpl Func {\n    pub(crate) fn as_debug_name(&self) -> &str {\n        let ident = self.name_hint.as_ref();\n\n        ident.map(|n| n.name.as_str()).unwrap_or(\"<anonymous>\")\n    }\n}\n\npub type WindowFrame = crate::ir::generic::WindowFrame<Box<Expr>>;\npub type ColumnSort = crate::ir::generic::ColumnSort<Box<Expr>>;\n\n/// FuncCall with better typing. Returns the modified table.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct TransformCall {\n    pub input: Box<Expr>,\n\n    pub kind: Box<TransformKind>,\n\n    /// Grouping of values in columns\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub partition: Option<Box<Expr>>,\n\n    /// Windowing frame of columns\n    #[serde(default, skip_serializing_if = \"WindowFrame::is_default\")]\n    pub frame: WindowFrame,\n\n    /// Windowing order of columns\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub sort: Vec<ColumnSort>,\n}\n\n#[derive(\n    Debug, PartialEq, Clone, Serialize, Deserialize, strum::AsRefStr, EnumAsInner, JsonSchema,\n)]\npub enum TransformKind {\n    Derive {\n        assigns: Box<Expr>,\n    },\n    Select {\n        assigns: Box<Expr>,\n    },\n    Filter {\n        filter: Box<Expr>,\n    },\n    Aggregate {\n        assigns: Box<Expr>,\n    },\n    Sort {\n        by: Vec<ColumnSort>,\n    },\n    Take {\n        range: Range,\n    },\n    Join {\n        side: JoinSide,\n        with: Box<Expr>,\n        filter: Box<Expr>,\n    },\n    Group {\n        by: Box<Expr>,\n        pipeline: Box<Expr>,\n    },\n    Window {\n        kind: WindowKind,\n        range: Range,\n        pipeline: Box<Expr>,\n    },\n    Append(Box<Expr>),\n    Loop(Box<Expr>),\n}\n\n/// A reference to a table that is not in scope of this query.\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]\npub enum TableExternRef {\n    /// Actual table in a database, that we can refer to by name in SQL\n    LocalTable(Ident),\n\n    /// Placeholder for a relation that will be provided later.\n    /// This is very similar to relational s-strings and may not even be needed for now, so\n    /// it's not documented anywhere. But it will be used in the future.\n    Param(String),\n    // TODO: add other sources such as files, URLs\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]\npub enum JoinSide {\n    Inner,\n    Left,\n    Right,\n    Full,\n}\n\nimpl Expr {\n    pub fn new(kind: impl Into<ExprKind>) -> Self {\n        Expr {\n            id: None,\n            kind: kind.into(),\n            span: None,\n            target_id: None,\n            ty: None,\n            lineage: None,\n            needs_window: false,\n            alias: None,\n            flatten: false,\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/pl/fold.rs",
    "content": "/// A trait to \"fold\" a PRQL AST (similar to a visitor), so we can transitively\n/// apply some logic to a whole tree by just defining how we want to handle each\n/// type.\nuse itertools::Itertools;\n\nuse super::*;\nuse crate::pr::{Ty, TyFunc, TyKind, TyTupleField};\nuse crate::Result;\n\n// Fold pattern:\n// - https://rust-unofficial.github.io/patterns/patterns/creational/fold.html\n// Good discussions on the visitor / fold pattern:\n// - https://github.com/rust-unofficial/patterns/discussions/236 (within this,\n//   this comment looked interesting: https://github.com/rust-unofficial/patterns/discussions/236#discussioncomment-393517)\n// - https://news.ycombinator.com/item?id=25620110\n\n// For some functions, we want to call a default impl, because copying &\n// pasting everything apart from a specific match is lots of repetition. So\n// we define a function outside the trait, by default call it, and let\n// implementors override the default while calling the function directly for\n// some cases. Ref https://stackoverflow.com/a/66077767/3064736\npub trait PlFold {\n    fn fold_stmt(&mut self, mut stmt: Stmt) -> Result<Stmt> {\n        stmt.kind = fold_stmt_kind(self, stmt.kind)?;\n        Ok(stmt)\n    }\n    fn fold_stmts(&mut self, stmts: Vec<Stmt>) -> Result<Vec<Stmt>> {\n        stmts.into_iter().map(|stmt| self.fold_stmt(stmt)).collect()\n    }\n    fn fold_expr(&mut self, mut expr: Expr) -> Result<Expr> {\n        expr.kind = self.fold_expr_kind(expr.kind)?;\n        Ok(expr)\n    }\n    fn fold_expr_kind(&mut self, expr_kind: ExprKind) -> Result<ExprKind> {\n        fold_expr_kind(self, expr_kind)\n    }\n    fn fold_exprs(&mut self, exprs: Vec<Expr>) -> Result<Vec<Expr>> {\n        exprs.into_iter().map(|node| self.fold_expr(node)).collect()\n    }\n    fn fold_var_def(&mut self, var_def: VarDef) -> Result<VarDef> {\n        fold_var_def(self, var_def)\n    }\n    fn fold_type_def(&mut self, ty_def: TypeDef) -> Result<TypeDef> {\n        Ok(TypeDef {\n            name: ty_def.name,\n            value: self.fold_type(ty_def.value)?,\n        })\n    }\n    fn fold_module_def(&mut self, module_def: ModuleDef) -> Result<ModuleDef> {\n        fold_module_def(self, module_def)\n    }\n    fn fold_func_call(&mut self, func_call: FuncCall) -> Result<FuncCall> {\n        fold_func_call(self, func_call)\n    }\n    fn fold_transform_call(&mut self, transform_call: TransformCall) -> Result<TransformCall> {\n        fold_transform_call(self, transform_call)\n    }\n    fn fold_func(&mut self, func: Func) -> Result<Func> {\n        fold_func(self, func)\n    }\n    fn fold_interpolate_item(&mut self, sstring_item: InterpolateItem) -> Result<InterpolateItem> {\n        fold_interpolate_item(self, sstring_item)\n    }\n    fn fold_type(&mut self, t: Ty) -> Result<Ty> {\n        fold_type(self, t)\n    }\n    fn fold_window(&mut self, window: WindowFrame) -> Result<WindowFrame> {\n        fold_window(self, window)\n    }\n}\n\npub fn fold_expr_kind<T: ?Sized + PlFold>(fold: &mut T, expr_kind: ExprKind) -> Result<ExprKind> {\n    use ExprKind::*;\n    Ok(match expr_kind {\n        Ident(ident) => Ident(ident),\n        All { within, except } => All {\n            within: Box::new(fold.fold_expr(*within)?),\n            except: Box::new(fold.fold_expr(*except)?),\n        },\n        Tuple(items) => Tuple(fold.fold_exprs(items)?),\n        Array(items) => Array(fold.fold_exprs(items)?),\n        SString(items) => SString(\n            items\n                .into_iter()\n                .map(|x| fold.fold_interpolate_item(x))\n                .try_collect()?,\n        ),\n        FString(items) => FString(\n            items\n                .into_iter()\n                .map(|x| fold.fold_interpolate_item(x))\n                .try_collect()?,\n        ),\n        Case(cases) => Case(fold_cases(fold, cases)?),\n\n        FuncCall(func_call) => FuncCall(fold.fold_func_call(func_call)?),\n        Func(closure) => Func(Box::new(fold.fold_func(*closure)?)),\n\n        TransformCall(transform) => TransformCall(fold.fold_transform_call(transform)?),\n        RqOperator { name, args } => RqOperator {\n            name,\n            args: fold.fold_exprs(args)?,\n        },\n\n        // None of these capture variables, so we don't need to fold them.\n        Param(_) | Internal(_) | Literal(_) => expr_kind,\n    })\n}\n\npub fn fold_stmt_kind<T: ?Sized + PlFold>(fold: &mut T, stmt_kind: StmtKind) -> Result<StmtKind> {\n    use StmtKind::*;\n    Ok(match stmt_kind {\n        VarDef(var_def) => VarDef(fold.fold_var_def(var_def)?),\n        TypeDef(type_def) => TypeDef(fold.fold_type_def(type_def)?),\n        ModuleDef(module_def) => ModuleDef(fold.fold_module_def(module_def)?),\n        QueryDef(_) | ImportDef(_) => stmt_kind,\n    })\n}\n\nfn fold_module_def<F: ?Sized + PlFold>(fold: &mut F, module_def: ModuleDef) -> Result<ModuleDef> {\n    Ok(ModuleDef {\n        name: module_def.name,\n        stmts: fold.fold_stmts(module_def.stmts)?,\n    })\n}\n\npub fn fold_var_def<F: ?Sized + PlFold>(fold: &mut F, var_def: VarDef) -> Result<VarDef> {\n    Ok(VarDef {\n        name: var_def.name,\n        value: fold_optional_box(fold, var_def.value)?,\n        ty: var_def.ty.map(|x| fold.fold_type(x)).transpose()?,\n    })\n}\n\npub fn fold_window<F: ?Sized + PlFold>(fold: &mut F, window: WindowFrame) -> Result<WindowFrame> {\n    Ok(WindowFrame {\n        kind: window.kind,\n        range: fold_range(fold, window.range)?,\n    })\n}\n\npub fn fold_range<F: ?Sized + PlFold>(fold: &mut F, Range { start, end }: Range) -> Result<Range> {\n    Ok(Range {\n        start: fold_optional_box(fold, start)?,\n        end: fold_optional_box(fold, end)?,\n    })\n}\n\n// This aren't strictly in the hierarchy, so we don't need to\n// have an assoc. function for `fold_optional_box` — we just\n// call out to the function in this module\npub fn fold_optional_box<F: ?Sized + PlFold>(\n    fold: &mut F,\n    opt: Option<Box<Expr>>,\n) -> Result<Option<Box<Expr>>> {\n    Ok(opt.map(|n| fold.fold_expr(*n)).transpose()?.map(Box::from))\n}\n\npub fn fold_interpolate_item<F: ?Sized + PlFold>(\n    fold: &mut F,\n    interpolate_item: InterpolateItem,\n) -> Result<InterpolateItem> {\n    Ok(match interpolate_item {\n        InterpolateItem::String(string) => InterpolateItem::String(string),\n        InterpolateItem::Expr { expr, format } => InterpolateItem::Expr {\n            expr: Box::new(fold.fold_expr(*expr)?),\n            format,\n        },\n    })\n}\n\nfn fold_cases<F: ?Sized + PlFold>(fold: &mut F, cases: Vec<SwitchCase>) -> Result<Vec<SwitchCase>> {\n    cases\n        .into_iter()\n        .map(|c| fold_switch_case(fold, c))\n        .try_collect()\n}\n\npub fn fold_switch_case<F: ?Sized + PlFold>(fold: &mut F, case: SwitchCase) -> Result<SwitchCase> {\n    Ok(SwitchCase {\n        condition: Box::new(fold.fold_expr(*case.condition)?),\n        value: Box::new(fold.fold_expr(*case.value)?),\n    })\n}\n\npub fn fold_column_sorts<F: ?Sized + PlFold>(\n    fold: &mut F,\n    sort: Vec<ColumnSort>,\n) -> Result<Vec<ColumnSort>> {\n    sort.into_iter()\n        .map(|s| fold_column_sort(fold, s))\n        .try_collect()\n}\n\npub fn fold_column_sort<T: ?Sized + PlFold>(\n    fold: &mut T,\n    sort_column: ColumnSort,\n) -> Result<ColumnSort> {\n    Ok(ColumnSort {\n        direction: sort_column.direction,\n        column: Box::new(fold.fold_expr(*sort_column.column)?),\n    })\n}\n\npub fn fold_func_call<T: ?Sized + PlFold>(fold: &mut T, func_call: FuncCall) -> Result<FuncCall> {\n    Ok(FuncCall {\n        name: Box::new(fold.fold_expr(*func_call.name)?),\n        args: fold.fold_exprs(func_call.args)?,\n        named_args: func_call\n            .named_args\n            .into_iter()\n            .map(|(name, expr)| fold.fold_expr(expr).map(|e| (name, e)))\n            .try_collect()?,\n    })\n}\n\npub fn fold_transform_call<T: ?Sized + PlFold>(\n    fold: &mut T,\n    t: TransformCall,\n) -> Result<TransformCall> {\n    Ok(TransformCall {\n        kind: Box::new(fold_transform_kind(fold, *t.kind)?),\n        input: Box::new(fold.fold_expr(*t.input)?),\n        partition: fold_optional_box(fold, t.partition)?,\n        frame: fold.fold_window(t.frame)?,\n        sort: fold_column_sorts(fold, t.sort)?,\n    })\n}\n\npub fn fold_transform_kind<T: ?Sized + PlFold>(\n    fold: &mut T,\n    t: TransformKind,\n) -> Result<TransformKind> {\n    use TransformKind::*;\n    Ok(match t {\n        Derive { assigns } => Derive {\n            assigns: Box::new(fold.fold_expr(*assigns)?),\n        },\n        Select { assigns } => Select {\n            assigns: Box::new(fold.fold_expr(*assigns)?),\n        },\n        Filter { filter } => Filter {\n            filter: Box::new(fold.fold_expr(*filter)?),\n        },\n        Aggregate { assigns } => Aggregate {\n            assigns: Box::new(fold.fold_expr(*assigns)?),\n        },\n        Sort { by } => Sort {\n            by: fold_column_sorts(fold, by)?,\n        },\n        Take { range } => Take {\n            range: fold_range(fold, range)?,\n        },\n        Join { side, with, filter } => Join {\n            side,\n            with: Box::new(fold.fold_expr(*with)?),\n            filter: Box::new(fold.fold_expr(*filter)?),\n        },\n        Append(bottom) => Append(Box::new(fold.fold_expr(*bottom)?)),\n        Group { by, pipeline } => Group {\n            by: Box::new(fold.fold_expr(*by)?),\n            pipeline: Box::new(fold.fold_expr(*pipeline)?),\n        },\n        Window {\n            kind,\n            range,\n            pipeline,\n        } => Window {\n            kind,\n            range: fold_range(fold, range)?,\n            pipeline: Box::new(fold.fold_expr(*pipeline)?),\n        },\n        Loop(pipeline) => Loop(Box::new(fold.fold_expr(*pipeline)?)),\n    })\n}\n\npub fn fold_func<T: ?Sized + PlFold>(fold: &mut T, func: Func) -> Result<Func> {\n    Ok(Func {\n        body: Box::new(fold.fold_expr(*func.body)?),\n        args: func\n            .args\n            .into_iter()\n            .map(|item| fold.fold_expr(item))\n            .try_collect()?,\n        ..func\n    })\n}\n\npub fn fold_func_param<T: ?Sized + PlFold>(\n    fold: &mut T,\n    nodes: Vec<FuncParam>,\n) -> Result<Vec<FuncParam>> {\n    nodes\n        .into_iter()\n        .map(|param| {\n            Ok(FuncParam {\n                default_value: fold_optional_box(fold, param.default_value)?,\n                ..param\n            })\n        })\n        .try_collect()\n}\n\n#[inline]\npub fn fold_type_opt<T: ?Sized + PlFold>(fold: &mut T, ty: Option<Ty>) -> Result<Option<Ty>> {\n    ty.map(|t| fold.fold_type(t)).transpose()\n}\n\n#[inline]\npub fn fold_type_opt_box<T: ?Sized + PlFold>(\n    fold: &mut T,\n    ty: Option<Box<Ty>>,\n) -> Result<Option<Box<Ty>>> {\n    ty.map(|t| fold.fold_type(*t).map(Box::new)).transpose()\n}\n\npub fn fold_type<T: ?Sized + PlFold>(fold: &mut T, ty: Ty) -> Result<Ty> {\n    Ok(Ty {\n        kind: match ty.kind {\n            TyKind::Tuple(fields) => TyKind::Tuple(\n                fields\n                    .into_iter()\n                    .map(|field| -> Result<_> {\n                        Ok(match field {\n                            TyTupleField::Single(name, ty) => {\n                                TyTupleField::Single(name, fold_type_opt(fold, ty)?)\n                            }\n                            TyTupleField::Wildcard(ty) => {\n                                TyTupleField::Wildcard(fold_type_opt(fold, ty)?)\n                            }\n                        })\n                    })\n                    .try_collect()?,\n            ),\n            TyKind::Array(ty) => TyKind::Array(fold_type_opt_box(fold, ty)?),\n            TyKind::Function(func) => TyKind::Function(\n                func.map(|f| -> Result<_> {\n                    Ok(TyFunc {\n                        params: f\n                            .params\n                            .into_iter()\n                            .map(|a| fold_type_opt(fold, a))\n                            .try_collect()?,\n                        return_ty: fold_type_opt(fold, f.return_ty.map(|x| *x))?.map(Box::new),\n                        name_hint: f.name_hint,\n                    })\n                })\n                .transpose()?,\n            ),\n            TyKind::Ident(_) | TyKind::Primitive(_) => ty.kind,\n        },\n        span: ty.span,\n        name: ty.name,\n    })\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/pl/lineage.rs",
    "content": "use std::collections::HashSet;\nuse std::fmt::{Debug, Display, Formatter};\n\nuse enum_as_inner::EnumAsInner;\nuse itertools::{Itertools, Position};\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize, Serializer};\n\nuse super::Ident;\n\n/// Represents the object that is manipulated by the pipeline transforms.\n/// Similar to a view in a database or a data frame.\n#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]\npub struct Lineage {\n    pub columns: Vec<LineageColumn>,\n\n    pub inputs: Vec<LineageInput>,\n\n    // A hack that allows name retention when applying `ExprKind::All { except }`\n    #[serde(skip)]\n    pub prev_columns: Vec<LineageColumn>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]\npub struct LineageInput {\n    /// Id of the node in AST that declares this input.\n    pub id: usize,\n\n    /// Local name of this input within a query.\n    pub name: String,\n\n    /// Fully qualified name of the table that provides the data for this input.\n    pub table: Ident,\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, EnumAsInner, JsonSchema)]\npub enum LineageColumn {\n    Single {\n        name: Option<Ident>,\n\n        // id of the defining expr (which can be actual expr or lineage input expr)\n        target_id: usize,\n\n        // if target is a relation, this is the name within the relation\n        target_name: Option<String>,\n    },\n\n    /// All columns (including unknown ones) from an input (i.e. `foo_table.*`)\n    All {\n        input_id: usize,\n\n        #[serde(serialize_with = \"sorted_set\")]\n        except: HashSet<String>,\n    },\n}\n\npub fn sorted_set<S: Serializer, V: Serialize + Ord>(\n    value: &HashSet<V>,\n    serializer: S,\n) -> Result<S::Ok, S::Error> {\n    value\n        .iter()\n        .sorted()\n        .collect::<Vec<_>>()\n        .serialize(serializer)\n}\n\nimpl Display for Lineage {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        display_lineage(self, f, false)\n    }\n}\n\nfn display_lineage(lineage: &Lineage, f: &mut Formatter, display_ids: bool) -> std::fmt::Result {\n    write!(f, \"[\")?;\n    for (pos, col) in lineage.columns.iter().with_position() {\n        let is_last = matches!(pos, Position::Last | Position::Only);\n        display_lineage_column(col, f, display_ids)?;\n        if !is_last {\n            write!(f, \", \")?;\n        }\n    }\n    write!(f, \"]\")\n}\n\nfn display_lineage_column(\n    col: &LineageColumn,\n    f: &mut Formatter,\n    display_ids: bool,\n) -> std::fmt::Result {\n    match col {\n        LineageColumn::All { input_id, .. } => {\n            write!(f, \"{input_id}.*\")?;\n        }\n        LineageColumn::Single {\n            name, target_id, ..\n        } => {\n            if let Some(name) = name {\n                write!(f, \"{name}\")?\n            } else {\n                write!(f, \"?\")?\n            }\n            if display_ids {\n                write!(f, \":{target_id}\")?\n            }\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/pl/mod.rs",
    "content": "//! Pipelined Language AST\n//!\n//! Abstract Syntax Tree for the first part of PRQL compiler.\n//! It can represent basic expressions, lists, pipelines, function calls &\n//! definitions, variable declarations and more.\n//!\n//! The central struct here is [Expr] and its [ExprKind].\n//!\n//! Top-level construct is a list of statements [`Vec<Stmt>`].\n\npub use crate::pr::Literal;\npub use crate::pr::QueryDef;\npub use crate::pr::{BinOp, BinaryExpr, Ident, UnOp, UnaryExpr};\n\npub use self::expr::*;\npub use self::extra::*;\npub use self::fold::*;\npub use self::lineage::*;\npub use self::stmt::*;\npub use self::utils::*;\n\nmod expr;\nmod extra;\nmod fold;\nmod lineage;\nmod stmt;\nmod utils;\n\npub fn print_mem_sizes() {\n    use std::mem::size_of;\n\n    use crate::ir::{decl, generic, pl, rq};\n    use crate::pr::{PrimitiveSet, Ty, TyFunc, TyKind, TyTupleField};\n    use crate::{ErrorMessage, ErrorMessages, SourceTree, Span};\n\n    println!(\"{:16}= {}\", \"Annotation\", size_of::<Annotation>());\n    println!(\"{:16}= {}\", \"BinaryExpr\", size_of::<BinaryExpr>());\n    println!(\"{:16}= {}\", \"BinOp\", size_of::<BinOp>());\n    println!(\"{:16}= {}\", \"ColumnSort\", size_of::<ColumnSort>());\n    println!(\"{:16}= {}\", \"decl::Decl\", size_of::<decl::Decl>());\n    println!(\"{:16}= {}\", \"decl::DeclKind\", size_of::<decl::DeclKind>());\n    println!(\"{:16}= {}\", \"decl::Module\", size_of::<decl::Module>());\n    println!(\"{:16}= {}\", \"decl::TableDecl\", size_of::<decl::TableDecl>());\n    println!(\"{:16}= {}\", \"decl::TableExpr\", size_of::<decl::TableExpr>());\n    println!(\"{:16}= {}\", \"ErrorMessage\", size_of::<ErrorMessage>());\n    println!(\"{:16}= {}\", \"ErrorMessages\", size_of::<ErrorMessages>());\n    println!(\"{:16}= {}\", \"ExprKind\", size_of::<ExprKind>());\n    println!(\"{:16}= {}\", \"Func\", size_of::<Func>());\n    println!(\"{:16}= {}\", \"FuncCall\", size_of::<FuncCall>());\n    println!(\"{:16}= {}\", \"FuncParam\", size_of::<FuncParam>());\n    println!(\n        \"{:16}= {}\",\n        \"generic::SortDirection\",\n        size_of::<generic::SortDirection>()\n    );\n    println!(\n        \"{:16}= {}\",\n        \"generic::WindowKind\",\n        size_of::<generic::WindowKind>()\n    );\n    println!(\"{:16}= {}\", \"InterpolateItem\", size_of::<InterpolateItem>());\n    println!(\"{:16}= {}\", \"JoinSide\", size_of::<JoinSide>());\n    println!(\"{:16}= {}\", \"Lineage\", size_of::<Lineage>());\n    println!(\"{:16}= {}\", \"LineageColumn\", size_of::<LineageColumn>());\n    println!(\"{:16}= {}\", \"LineageInput\", size_of::<LineageInput>());\n    println!(\"{:16}= {}\", \"ModuleDef\", size_of::<ModuleDef>());\n    println!(\"{:16}= {}\", \"pl::Expr\", size_of::<pl::Expr>());\n    println!(\"{:16}= {}\", \"PrimitiveSet\", size_of::<PrimitiveSet>());\n    println!(\"{:16}= {}\", \"QueryDef\", size_of::<QueryDef>());\n    println!(\"{:16}= {}\", \"Range\", size_of::<Range>());\n    println!(\"{:16}= {}\", \"rq::Expr\", size_of::<rq::Expr>());\n    println!(\n        \"{:16}= {}\",\n        \"rq::RelationalQuery\",\n        size_of::<rq::RelationalQuery>()\n    );\n    println!(\"{:16}= {}\", \"rq::TableRef\", size_of::<rq::TableRef>());\n    println!(\"{:16}= {}\", \"SourceTree\", size_of::<SourceTree>());\n    println!(\"{:16}= {}\", \"Span\", size_of::<Span>());\n    println!(\"{:16}= {}\", \"Stmt\", size_of::<Stmt>());\n    println!(\"{:16}= {}\", \"StmtKind\", size_of::<StmtKind>());\n    println!(\"{:16}= {}\", \"SwitchCase\", size_of::<SwitchCase>());\n    println!(\"{:16}= {}\", \"TableExternRef\", size_of::<TableExternRef>());\n    println!(\"{:16}= {}\", \"TransformCall\", size_of::<TransformCall>());\n    println!(\"{:16}= {}\", \"TransformKind\", size_of::<TransformKind>());\n    println!(\"{:16}= {}\", \"TupleField\", size_of::<TyTupleField>());\n    println!(\"{:16}= {}\", \"Ty\", size_of::<Ty>());\n    println!(\"{:16}= {}\", \"TyFunc\", size_of::<TyFunc>());\n    println!(\"{:16}= {}\", \"TyKind\", size_of::<TyKind>());\n    println!(\"{:16}= {}\", \"TyOrExpr\", size_of::<TyOrExpr>());\n    println!(\"{:16}= {}\", \"TypeDef\", size_of::<TypeDef>());\n    println!(\"{:16}= {}\", \"UnaryExpr\", size_of::<UnaryExpr>());\n    println!(\"{:16}= {}\", \"UnOp\", size_of::<UnOp>());\n    println!(\"{:16}= {}\", \"VarDef\", size_of::<VarDef>());\n    println!(\"{:16}= {}\", \"WindowFrame\", size_of::<WindowFrame>());\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/pl/stmt.rs",
    "content": "use enum_as_inner::EnumAsInner;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse crate::pr::Ident;\nuse crate::pr::QueryDef;\nuse crate::pr::{Span, Ty};\n\nuse super::expr::Expr;\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct Stmt {\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub id: Option<usize>,\n    #[serde(flatten)]\n    pub kind: StmtKind,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub span: Option<Span>,\n\n    #[serde(skip_serializing_if = \"Vec::is_empty\", default)]\n    pub annotations: Vec<Annotation>,\n}\n\n#[derive(Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub enum StmtKind {\n    QueryDef(Box<QueryDef>),\n    VarDef(VarDef),\n    TypeDef(TypeDef),\n    ModuleDef(ModuleDef),\n    ImportDef(ImportDef),\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct VarDef {\n    pub name: String,\n    pub value: Option<Box<Expr>>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub ty: Option<Ty>,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct TypeDef {\n    pub name: String,\n    pub value: Ty,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct ModuleDef {\n    pub name: String,\n    pub stmts: Vec<Stmt>,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct ImportDef {\n    pub alias: Option<String>,\n    pub name: Ident,\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct Annotation {\n    pub expr: Box<Expr>,\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/pl/utils.rs",
    "content": "use super::{Expr, ExprKind, FuncCall};\nuse crate::pr::Ident;\n\npub fn maybe_binop(left: Option<Expr>, op_name: &[&str], right: Option<Expr>) -> Option<Expr> {\n    match (left, right) {\n        (Some(left), Some(right)) => Some(new_binop(left, op_name, right)),\n        (left, right) => left.or(right),\n    }\n}\n\npub fn new_binop(left: Expr, op_name: &[&str], right: Expr) -> Expr {\n    Expr::new(ExprKind::FuncCall(FuncCall {\n        name: Box::new(Expr::new(Ident::from_path(op_name.to_vec()))),\n        args: vec![left, right],\n        named_args: Default::default(),\n    }))\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/rq/expr.rs",
    "content": "use enum_as_inner::EnumAsInner;\nuse prqlc_parser::generic;\nuse prqlc_parser::lexer::lr::Literal;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse super::CId;\nuse crate::Span;\n\n/// Analogous to [crate::ir::pl::Expr], but with fewer kinds.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct Expr {\n    pub kind: ExprKind,\n    pub span: Option<Span>,\n}\n\npub(super) type Range = generic::Range<Expr>;\npub(super) type InterpolateItem = generic::InterpolateItem<Expr>;\npub(super) type SwitchCase = generic::SwitchCase<Expr>;\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, EnumAsInner, JsonSchema)]\npub enum ExprKind {\n    ColumnRef(CId),\n    // https://github.com/dtolnay/serde-yaml/issues/363\n    // We should repeat this if we encounter any other nested enums.\n    #[cfg_attr(\n        feature = \"serde_yaml\",\n        serde(with = \"serde_yaml::with::singleton_map\"),\n        schemars(with = \"Literal\")\n    )]\n    Literal(Literal),\n\n    SString(Vec<InterpolateItem>),\n\n    Case(Vec<SwitchCase>),\n\n    Operator {\n        name: String,\n        args: Vec<Expr>,\n    },\n\n    /// Placeholder for expressions provided after compilation.\n    Param(String),\n\n    Array(Vec<Expr>),\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]\npub enum UnOp {\n    Neg,\n    Not,\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/rq/fold.rs",
    "content": "/// A trait to \"fold\" a PRQL AST (similar to a visitor), so we can transitively\n/// apply some logic to a whole tree by just defining how we want to handle each\n/// type.\nuse itertools::Itertools;\n\nuse super::*;\nuse crate::ir::generic::{ColumnSort, WindowFrame};\nuse crate::Result;\n\n// Fold pattern:\n// - https://rust-unofficial.github.io/patterns/patterns/creational/fold.html\n// Good discussions on the visitor / fold pattern:\n// - https://github.com/rust-unofficial/patterns/discussions/236 (within this,\n//   this comment looked interesting: https://github.com/rust-unofficial/patterns/discussions/236#discussioncomment-393517)\n// - https://news.ycombinator.com/item?id=25620110\n\n// For some functions, we want to call a default impl, because copying &\n// pasting everything apart from a specific match is lots of repetition. So\n// we define a function outside the trait, by default call it, and let\n// implementors override the default while calling the function directly for\n// some cases. Ref https://stackoverflow.com/a/66077767/3064736\npub trait RqFold {\n    fn fold_transform(&mut self, transform: Transform) -> Result<Transform> {\n        fold_transform(self, transform)\n    }\n    fn fold_transforms(&mut self, transforms: Vec<Transform>) -> Result<Vec<Transform>> {\n        fold_transforms(self, transforms)\n    }\n    fn fold_table(&mut self, table: TableDecl) -> Result<TableDecl> {\n        fold_table(self, table)\n    }\n    fn fold_relation(&mut self, relation: Relation) -> Result<Relation> {\n        fold_relation(self, relation)\n    }\n    fn fold_relation_kind(&mut self, rel_kind: RelationKind) -> Result<RelationKind> {\n        fold_relation_kind(self, rel_kind)\n    }\n    fn fold_table_ref(&mut self, table_ref: TableRef) -> Result<TableRef> {\n        fold_table_ref(self, table_ref)\n    }\n    fn fold_query(&mut self, query: RelationalQuery) -> Result<RelationalQuery> {\n        fold_query(self, query)\n    }\n    fn fold_expr(&mut self, mut expr: Expr) -> Result<Expr> {\n        expr.kind = self.fold_expr_kind(expr.kind)?;\n        Ok(expr)\n    }\n    fn fold_expr_kind(&mut self, kind: ExprKind) -> Result<ExprKind> {\n        fold_expr_kind(self, kind)\n    }\n    fn fold_relation_column(&mut self, col: RelationColumn) -> Result<RelationColumn> {\n        Ok(col)\n    }\n    fn fold_cid(&mut self, cid: CId) -> Result<CId> {\n        Ok(cid)\n    }\n    fn fold_cids(&mut self, cids: Vec<CId>) -> Result<Vec<CId>> {\n        cids.into_iter().map(|i| self.fold_cid(i)).try_collect()\n    }\n    fn fold_compute(&mut self, compute: Compute) -> Result<Compute> {\n        fold_compute(self, compute)\n    }\n}\n\nfn fold_compute<F: ?Sized + RqFold>(fold: &mut F, compute: Compute) -> Result<Compute> {\n    Ok(Compute {\n        id: fold.fold_cid(compute.id)?,\n        expr: fold.fold_expr(compute.expr)?,\n        window: compute.window.map(|w| fold_window(fold, w)).transpose()?,\n        is_aggregation: compute.is_aggregation,\n    })\n}\n\nfn fold_window<F: ?Sized + RqFold>(fold: &mut F, w: Window) -> Result<Window> {\n    Ok(Window {\n        frame: WindowFrame {\n            kind: w.frame.kind,\n            range: Range {\n                start: w.frame.range.start.map(|x| fold.fold_expr(x)).transpose()?,\n                end: w.frame.range.end.map(|x| fold.fold_expr(x)).transpose()?,\n            },\n        },\n        partition: fold.fold_cids(w.partition)?,\n        sort: fold_column_sorts(fold, w.sort)?,\n    })\n}\n\npub fn fold_table<F: ?Sized + RqFold>(fold: &mut F, t: TableDecl) -> Result<TableDecl> {\n    Ok(TableDecl {\n        id: t.id,\n        name: t.name,\n        relation: fold.fold_relation(t.relation)?,\n    })\n}\n\npub fn fold_relation<F: ?Sized + RqFold>(fold: &mut F, relation: Relation) -> Result<Relation> {\n    Ok(Relation {\n        kind: fold.fold_relation_kind(relation.kind)?,\n        columns: relation.columns,\n    })\n}\n\npub fn fold_relation_kind<F: ?Sized + RqFold>(\n    fold: &mut F,\n    rel: RelationKind,\n) -> Result<RelationKind> {\n    Ok(match rel {\n        RelationKind::ExternRef(table_ref) => RelationKind::ExternRef(table_ref),\n        RelationKind::Pipeline(transforms) => {\n            RelationKind::Pipeline(fold.fold_transforms(transforms)?)\n        }\n        RelationKind::Literal(lit) => RelationKind::Literal(lit),\n        RelationKind::SString(items) => RelationKind::SString(fold_interpolate_items(fold, items)?),\n        RelationKind::BuiltInFunction { name, args } => RelationKind::BuiltInFunction {\n            name,\n            args: args.into_iter().map(|a| fold.fold_expr(a)).try_collect()?,\n        },\n    })\n}\n\npub fn fold_table_ref<F: ?Sized + RqFold>(fold: &mut F, table_ref: TableRef) -> Result<TableRef> {\n    Ok(TableRef {\n        name: table_ref.name,\n        source: table_ref.source,\n        columns: table_ref\n            .columns\n            .into_iter()\n            .map(|(col, cid)| -> Result<_> {\n                Ok((fold.fold_relation_column(col)?, fold.fold_cid(cid)?))\n            })\n            .try_collect()?,\n        prefer_cte: table_ref.prefer_cte,\n    })\n}\n\npub fn fold_query<F: ?Sized + RqFold>(\n    fold: &mut F,\n    query: RelationalQuery,\n) -> Result<RelationalQuery> {\n    Ok(RelationalQuery {\n        def: query.def,\n        relation: fold.fold_relation(query.relation)?,\n        tables: query\n            .tables\n            .into_iter()\n            .map(|t| fold.fold_table(t))\n            .try_collect()?,\n    })\n}\n\npub fn fold_transforms<F: ?Sized + RqFold>(\n    fold: &mut F,\n    transforms: Vec<Transform>,\n) -> Result<Vec<Transform>> {\n    transforms\n        .into_iter()\n        .map(|t| fold.fold_transform(t))\n        .try_collect()\n}\n\npub fn fold_transform<T: ?Sized + RqFold>(\n    fold: &mut T,\n    mut transform: Transform,\n) -> Result<Transform> {\n    use Transform::*;\n\n    transform = match transform {\n        From(tid) => From(fold.fold_table_ref(tid)?),\n\n        Compute(compute) => Compute(fold.fold_compute(compute)?),\n        Aggregate { partition, compute } => Aggregate {\n            partition: fold.fold_cids(partition)?,\n            compute: fold.fold_cids(compute)?,\n        },\n        Select(ids) => Select(fold.fold_cids(ids)?),\n        Filter(i) => Filter(fold.fold_expr(i)?),\n        Sort(sorts) => Sort(fold_column_sorts(fold, sorts)?),\n        Take(take) => Take(super::Take {\n            partition: fold.fold_cids(take.partition)?,\n            sort: fold_column_sorts(fold, take.sort)?,\n            range: take.range,\n        }),\n        Join { side, with, filter } => Join {\n            side,\n            with: fold.fold_table_ref(with)?,\n            filter: fold.fold_expr(filter)?,\n        },\n        Append(bottom) => Append(fold.fold_table_ref(bottom)?),\n        Loop(transforms) => Loop(fold_transforms(fold, transforms)?),\n    };\n    Ok(transform)\n}\n\npub fn fold_column_sorts<T: ?Sized + RqFold>(\n    fold: &mut T,\n    sorts: Vec<ColumnSort<CId>>,\n) -> Result<Vec<ColumnSort<CId>>> {\n    sorts\n        .into_iter()\n        .map(|s| -> Result<ColumnSort<CId>> {\n            Ok(ColumnSort {\n                column: fold.fold_cid(s.column)?,\n                direction: s.direction,\n            })\n        })\n        .try_collect()\n}\n\npub fn fold_expr_kind<F: ?Sized + RqFold>(fold: &mut F, kind: ExprKind) -> Result<ExprKind> {\n    Ok(match kind {\n        ExprKind::ColumnRef(cid) => ExprKind::ColumnRef(fold.fold_cid(cid)?),\n\n        ExprKind::SString(items) => ExprKind::SString(fold_interpolate_items(fold, items)?),\n        ExprKind::Case(cases) => ExprKind::Case(\n            cases\n                .into_iter()\n                .map(|c| fold_switch_case(fold, c))\n                .try_collect()?,\n        ),\n        ExprKind::Operator { name, args } => ExprKind::Operator {\n            name,\n            args: args.into_iter().map(|a| fold.fold_expr(a)).try_collect()?,\n        },\n        ExprKind::Param(id) => ExprKind::Param(id),\n\n        ExprKind::Literal(_) => kind,\n        ExprKind::Array(exprs) => {\n            ExprKind::Array(exprs.into_iter().map(|e| fold.fold_expr(e)).try_collect()?)\n        }\n    })\n}\n\n/// Helper\npub fn fold_optional_box<F: ?Sized + RqFold>(\n    fold: &mut F,\n    opt: Option<Box<Expr>>,\n) -> Result<Option<Box<Expr>>> {\n    Ok(match opt {\n        Some(e) => Some(Box::new(fold.fold_expr(*e)?)),\n        None => None,\n    })\n}\n\npub fn fold_interpolate_items<T: ?Sized + RqFold>(\n    fold: &mut T,\n    items: Vec<InterpolateItem>,\n) -> Result<Vec<InterpolateItem>> {\n    items\n        .into_iter()\n        .map(|i| fold_interpolate_item(fold, i))\n        .try_collect()\n}\n\npub fn fold_interpolate_item<T: ?Sized + RqFold>(\n    fold: &mut T,\n    item: InterpolateItem,\n) -> Result<InterpolateItem> {\n    Ok(match item {\n        InterpolateItem::String(string) => InterpolateItem::String(string),\n        InterpolateItem::Expr { expr, format } => InterpolateItem::Expr {\n            expr: Box::new(fold.fold_expr(*expr)?),\n            format,\n        },\n    })\n}\n\npub fn fold_switch_case<F: ?Sized + RqFold>(fold: &mut F, case: SwitchCase) -> Result<SwitchCase> {\n    Ok(SwitchCase {\n        condition: fold.fold_expr(case.condition)?,\n        value: fold.fold_expr(case.value)?,\n    })\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/rq/ids.rs",
    "content": "use schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n/// Column id\n#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Ord, PartialOrd, JsonSchema)]\npub struct CId(usize);\n\nimpl CId {\n    pub fn get(&self) -> usize {\n        self.0\n    }\n}\n\nimpl From<usize> for CId {\n    fn from(id: usize) -> Self {\n        CId(id)\n    }\n}\n\nimpl std::fmt::Debug for CId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"column-{}\", self.0)\n    }\n}\n\n/// Table id\n#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]\npub struct TId(usize);\n\nimpl TId {\n    pub fn get(&self) -> usize {\n        self.0\n    }\n}\n\nimpl From<usize> for TId {\n    fn from(id: usize) -> Self {\n        TId(id)\n    }\n}\n\nimpl std::fmt::Debug for TId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"table-{}\", self.0)\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/rq/mod.rs",
    "content": "//! Relational Query AST\n//!\n//! Strictly typed AST for describing relational queries.\n\nuse enum_as_inner::EnumAsInner;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\npub use expr::{Expr, ExprKind, UnOp};\nuse expr::{InterpolateItem, Range, SwitchCase};\npub use fold::*;\npub use ids::*;\nuse prqlc_parser::lexer::lr;\npub use transform::*;\npub use utils::*;\n\nuse super::pl::QueryDef;\nuse super::pl::TableExternRef;\n\nmod expr;\nmod fold;\nmod ids;\nmod transform;\nmod utils;\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct RelationalQuery {\n    pub def: QueryDef,\n\n    pub tables: Vec<TableDecl>,\n    pub relation: Relation,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct Relation {\n    pub kind: RelationKind,\n\n    /// Column definitions.\n    /// This is the interface of the table that can be referenced from other tables.\n    pub columns: Vec<RelationColumn>,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, EnumAsInner, JsonSchema)]\npub enum RelationKind {\n    #[cfg_attr(\n        feature = \"serde_yaml\",\n        serde(with = \"serde_yaml::with::singleton_map\"),\n        schemars(with = \"TableExternRef\")\n    )]\n    ExternRef(TableExternRef),\n    Pipeline(Vec<Transform>),\n    Literal(RelationLiteral),\n    SString(Vec<InterpolateItem>),\n    BuiltInFunction {\n        name: String,\n        args: Vec<Expr>,\n    },\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct RelationLiteral {\n    /// Column names\n    pub columns: Vec<String>,\n    /// Row-oriented data\n    pub rows: Vec<Vec<lr::Literal>>,\n}\n\n#[derive(Debug, PartialEq, Clone, Eq, Hash, Serialize, Deserialize, EnumAsInner, JsonSchema)]\npub enum RelationColumn {\n    /// A single column that may have a name.\n    /// Unnamed columns cannot be referenced.\n    Single(Option<String>),\n\n    /// Means \"and other unmentioned columns\". Does not mean \"all columns\".\n    Wildcard,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct TableDecl {\n    /// An id for this table, unique within all tables in this query.\n    pub id: TId,\n\n    /// Name hint for this declaration (name of the CTE)\n    pub name: Option<String>,\n\n    /// Table's contents.\n    pub relation: Relation,\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct TableRef {\n    /// Referenced table\n    pub source: TId,\n\n    /// New column definitions are required because there may be multiple instances\n    /// of this table in the same query\n    pub columns: Vec<(RelationColumn, CId)>,\n\n    /// Name hint for relation within this pipeline (table alias)\n    pub name: Option<String>,\n\n    /// We prefer CTEs for most syntaxes but some like UNION works best with subqueries.\n    pub prefer_cte: bool,\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/rq/transform.rs",
    "content": "use enum_as_inner::EnumAsInner;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse super::*;\nuse crate::ir::generic::ColumnSort;\nuse crate::ir::generic::WindowFrame;\nuse crate::ir::pl::JoinSide;\n\n/// Transformation of a table.\n#[derive(\n    Debug, PartialEq, Clone, Serialize, Deserialize, strum::AsRefStr, EnumAsInner, JsonSchema,\n)]\npub enum Transform {\n    From(TableRef),\n    Compute(Compute),\n    Select(Vec<CId>),\n    Filter(Expr),\n    Aggregate {\n        partition: Vec<CId>,\n        compute: Vec<CId>,\n    },\n    Sort(Vec<ColumnSort<CId>>),\n    Take(Take),\n    Join {\n        side: JoinSide,\n        with: TableRef,\n        filter: Expr,\n    },\n    Append(TableRef),\n    Loop(Vec<Transform>),\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct Take {\n    pub range: Range,\n    pub partition: Vec<CId>,\n    pub sort: Vec<ColumnSort<CId>>,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct Compute {\n    pub id: CId,\n    pub expr: Expr,\n\n    /// Parameters for window functions (or expressions).\n    #[serde(skip_serializing_if = \"Option::is_none\", default)]\n    pub window: Option<Window>,\n\n    /// Must be set exactly on columns used in [Transform::Aggregate].\n    #[serde(skip_serializing_if = \"is_false\", default)]\n    pub is_aggregation: bool,\n}\n\n/// Transformation of a table.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct Window {\n    pub frame: WindowFrame<Expr>,\n    pub partition: Vec<CId>,\n    pub sort: Vec<ColumnSort<CId>>,\n}\n\nfn is_false(b: &bool) -> bool {\n    !b\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/ir/rq/utils.rs",
    "content": "use super::Expr;\nuse super::ExprKind;\n\npub fn new_binop(left: Expr, operator_name: &str, right: Expr) -> Expr {\n    Expr {\n        kind: ExprKind::Operator {\n            name: operator_name.to_string(),\n            args: vec![left, right],\n        },\n        span: None,\n    }\n}\n\npub fn maybe_binop(left: Option<Expr>, operator_name: &str, right: Option<Expr>) -> Option<Expr> {\n    match (left, right) {\n        (Some(left), Some(right)) => Some(Expr {\n            kind: ExprKind::Operator {\n                name: operator_name.to_string(),\n                args: vec![left, right],\n            },\n            span: None,\n        }),\n        (left, right) => left.or(right),\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/lib.rs",
    "content": "//! # prqlc\n//!\n//! Compiler for PRQL language. Targets SQL and exposes PL and RQ abstract\n//! syntax trees.\n//!\n//! You probably want to start with [compile] wrapper function.\n//!\n//! For more granular access, refer to this diagram:\n//! ```ascii\n//!            PRQL\n//!\n//!    (parse) │ ▲\n//! prql_to_pl │ │ pl_to_prql\n//!            │ │\n//!            ▼ │      json::from_pl\n//!                   ────────►\n//!           PL AST            PL JSON\n//!                   ◄────────\n//!            │        json::to_pl\n//!            │\n//!  (resolve) │\n//!   pl_to_rq │\n//!            │\n//!            │\n//!            ▼        json::from_rq\n//!                   ────────►\n//!           RQ AST            RQ JSON\n//!                   ◄────────\n//!            │        json::to_rq\n//!            │\n//!  rq_to_sql │\n//!            ▼\n//!\n//!            SQL\n//! ```\n//!\n#![doc = include_str!(\"../ARCHITECTURE.md\")]\n// TODO: remove when enum-as-inner is updated with the fix from\n// https://github.com/bluejekyll/enum-as-inner/pull/108\n// This suppresses false positive warnings from Rust 1.92+:\n// https://github.com/rust-lang/rust/issues/147648\n#![allow(unused_assignments)]\n//!\n//! ## Common use-cases\n//!\n//! - Compile PRQL queries to SQL at run time.\n//!\n//!   ```\n//!   # fn main() -> Result<(), prqlc::ErrorMessages> {\n//!   let sql = prqlc::compile(\n//!       \"from albums | select {title, artist_id}\",\n//!        &prqlc::Options::default().no_format()\n//!   )?;\n//!   assert_eq!(&sql[..35], \"SELECT title, artist_id FROM albums\");\n//!   # Ok(())\n//!   # }\n//!   ```\n//!\n//! - Compile PRQL queries to SQL at build time.\n//!\n//!   For inline strings, use the `prqlc-macros` crate; for example:\n//!   ```ignore\n//!   let sql: &str = prql_to_sql!(\"from albums | select {title, artist_id}\");\n//!   ```\n//!\n//!   For compiling whole files (`.prql` to `.sql`), call `prqlc` from\n//!   `build.rs`. See [this example\n//!   project](https://github.com/PRQL/prql/tree/main/prqlc/prqlc/examples/compile-files).\n//!\n//! - Compile, format & debug PRQL from command line.\n//!\n//!   ```sh\n//!   $ cargo install --locked prqlc\n//!   $ prqlc compile query.prql\n//!   ```\n//!\n//! ## Feature flags\n//!\n//! The following feature flags are available:\n//!\n//! * `cli`: enables the `prqlc` CLI binary. This is enabled by default. When\n//!   consuming this crate from another rust library, it can be disabled.\n//! * `test-dbs`: enables the `prqlc` in-process test databases as part of the\n//!   crate's tests. This significantly increases compile times so is not\n//!   enabled by default.\n//! * `test-dbs-external`: enables the `prqlc` external test databases,\n//!   requiring a docker container with the test databases to be running. Check\n//!   out the [integration tests](https://github.com/PRQL/prql/tree/main/prqlc/prqlc/tests/integration/dbs)\n//!   for more details.\n//! * `serde_yaml`: Enables serialization and deserialization of ASTs to YAML.\n//!\n//! ## Large binary sizes\n//!\n//! For Linux users, the binary size contributed by this crate will probably be\n//! quite large (>20MB) by default. That is because it includes a lot of\n//! debuginfo symbols from our parser. They can be removed by adding the\n//! following to `Cargo.toml`, reducing the contribution to around 7MB:\n//! ```toml\n//! [profile.release.package.prqlc]\n//! strip = \"debuginfo\"\n//! ```\n\nuse std::sync::OnceLock;\nuse std::{collections::HashMap, path::PathBuf, str::FromStr};\n\nuse anstream::adapter::strip_str;\nuse semver::Version;\nuse serde::{Deserialize, Serialize};\nuse strum::VariantNames;\n\npub use error_message::{ErrorMessage, ErrorMessages, SourceLocation};\npub use prqlc_parser::error::{Error, ErrorSource, Errors, MessageKind, Reason, WithErrorInfo};\npub use prqlc_parser::lexer::lr;\npub use prqlc_parser::parser::pr;\npub use prqlc_parser::span::Span;\n\nmod codegen;\npub mod debug;\nmod error_message;\npub mod ir;\npub mod parser;\npub mod semantic;\npub mod sql;\n#[cfg(feature = \"cli\")]\npub mod utils;\n#[cfg(not(feature = \"cli\"))]\npub(crate) mod utils;\n\npub type Result<T, E = Error> = core::result::Result<T, E>;\n\n/// Get the version of the compiler. This is determined by the first of:\n/// - An optional environment variable `PRQL_VERSION_OVERRIDE`; primarily useful\n///   for internal testing.\n///   - Note that this env var is checked on every call of this function.\n///     Without checking each read, we found some internal tests were flaky. If\n///     this caused any perf issues, we could adjust the tests that rely on\n///     versions to run in a more encapsulated way (for example, use `prqlc`\n///     binary tests, which we can guarantee won't have anything call this\n///     before setting up the env var).\n/// - The version returned by `git describe --tags`\n/// - The version in the cargo manifest\npub fn compiler_version() -> Version {\n    if let Ok(prql_version_override) = std::env::var(\"PRQL_VERSION_OVERRIDE\") {\n        return Version::parse(&prql_version_override).unwrap_or_else(|e| {\n            panic!(\"Could not parse PRQL version {prql_version_override}\\n{e}\")\n        });\n    };\n\n    static COMPILER_VERSION: OnceLock<Version> = OnceLock::new();\n    COMPILER_VERSION\n        .get_or_init(|| {\n            if let Ok(prql_version_override) = std::env::var(\"PRQL_VERSION_OVERRIDE\") {\n                return Version::parse(&prql_version_override).unwrap_or_else(|e| {\n                    panic!(\"Could not parse PRQL version {prql_version_override}\\n{e}\")\n                });\n            }\n            let git_version = env!(\"VERGEN_GIT_DESCRIBE\");\n            let cargo_version = env!(\"CARGO_PKG_VERSION\");\n            Version::parse(git_version)\n                .or_else(|e| {\n                    log::info!(\"Could not parse git version number {git_version}\\n{e}\");\n                    Version::parse(cargo_version)\n                })\n                .unwrap_or_else(|e| {\n                    panic!(\"Could not parse prqlc version number {cargo_version}\\n{e}\")\n                })\n        })\n        .clone()\n}\n\n/// Compile a PRQL string into a SQL string.\n///\n/// This is a wrapper for:\n/// - [prql_to_pl] — Build PL AST from a PRQL string\n/// - [pl_to_rq] — Finds variable references, validates functions calls,\n///   determines frames and converts PL to RQ.\n/// - [rq_to_sql] — Convert RQ AST into an SQL string.\n/// # Example\n/// Use the prql compiler to convert a PRQL string to SQLite dialect\n///\n/// ```\n/// use prqlc::{compile, Options, Target, sql::Dialect};\n///\n/// let prql = \"from employees | select {name,age}\";\n/// let opts = Options::default().with_target(Target::Sql(Some(Dialect::SQLite))).with_signature_comment(false).with_format(false);\n/// let sql = compile(&prql, &opts).unwrap();\n/// println!(\"PRQL: {}\\nSQLite: {}\", prql, &sql);\n/// assert_eq!(\"SELECT name, age FROM employees\", sql)\n///\n/// ```\n/// See [`sql::Options`](sql/struct.Options.html) and\n/// [`sql::Dialect`](sql/enum.Dialect.html) for options and supported SQL\n/// dialects.\npub fn compile(prql: &str, options: &Options) -> Result<String, ErrorMessages> {\n    let sources = SourceTree::from(prql);\n\n    Ok(&sources)\n        .and_then(parser::parse)\n        .and_then(|ast| {\n            semantic::resolve_and_lower(ast, &[], None)\n                .map_err(|e| e.with_source(ErrorSource::NameResolver).into())\n        })\n        .and_then(|rq| {\n            sql::compile(rq, options).map_err(|e| e.with_source(ErrorSource::SQL).into())\n        })\n        .map_err(|e| {\n            let error_messages = ErrorMessages::from(e).composed(&sources);\n            match options.display {\n                DisplayOptions::AnsiColor => error_messages,\n                DisplayOptions::Plain => ErrorMessages {\n                    inner: error_messages\n                        .inner\n                        .into_iter()\n                        .map(|e| ErrorMessage {\n                            display: e.display.map(|s| strip_str(&s).to_string()),\n                            ..e\n                        })\n                        .collect(),\n                },\n            }\n        })\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub enum Target {\n    /// If `None` is used, dialect is extracted from `target` query header.\n    Sql(Option<sql::Dialect>),\n}\n\nimpl Default for Target {\n    fn default() -> Self {\n        Self::Sql(None)\n    }\n}\n\nimpl Target {\n    pub fn names() -> Vec<String> {\n        let mut names = vec![\"sql.any\".to_string()];\n\n        let dialects = sql::Dialect::VARIANTS;\n        names.extend(dialects.iter().map(|d| format!(\"sql.{d}\")));\n\n        names\n    }\n}\n\nimpl FromStr for Target {\n    type Err = Error;\n\n    fn from_str(s: &str) -> Result<Target, Self::Err> {\n        if let Some(dialect) = s.strip_prefix(\"sql.\") {\n            if dialect == \"any\" {\n                return Ok(Target::Sql(None));\n            }\n\n            if let Ok(dialect) = sql::Dialect::from_str(dialect) {\n                return Ok(Target::Sql(Some(dialect)));\n            }\n        }\n\n        Err(Error::new(Reason::NotFound {\n            name: format!(\"{s:?}\"),\n            namespace: \"target\".to_string(),\n        }))\n    }\n}\n\n/// Compilation options for SQL backend of the compiler.\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Options {\n    /// Pass generated SQL string through a formatter that splits it\n    /// into multiple lines and prettifies indentation and spacing.\n    ///\n    /// Defaults to true.\n    pub format: bool,\n\n    /// Target and dialect to compile to.\n    pub target: Target,\n\n    /// Emits the compiler signature as a comment after generated SQL\n    ///\n    /// Defaults to true.\n    pub signature_comment: bool,\n\n    /// Deprecated: use `display` instead.\n    pub color: bool,\n\n    /// Whether to use ANSI colors in error messages. This may be extended to\n    /// other formats in the future.\n    ///\n    /// Note that we don't generally recommend threading a `color` option\n    /// through an entire application. Instead, in order of preferences:\n    /// - Use a library such as `anstream` to encapsulate presentation logic and\n    ///   automatically disable colors when not connected to a TTY.\n    /// - Set an environment variable such as `CLI_COLOR=0` to disable any\n    ///   colors coming back from this library.\n    /// - Strip colors from the output (possibly also with a library such as\n    ///   `anstream`).\n    pub display: DisplayOptions,\n}\n\nimpl Default for Options {\n    fn default() -> Self {\n        Self {\n            format: true,\n            target: Target::Sql(None),\n            signature_comment: true,\n            color: true,\n            display: DisplayOptions::AnsiColor,\n        }\n    }\n}\n\nimpl Options {\n    pub fn with_format(mut self, format: bool) -> Self {\n        self.format = format;\n        self\n    }\n\n    pub fn no_format(self) -> Self {\n        self.with_format(false)\n    }\n\n    pub fn with_signature_comment(mut self, signature_comment: bool) -> Self {\n        self.signature_comment = signature_comment;\n        self\n    }\n\n    pub fn no_signature(self) -> Self {\n        self.with_signature_comment(false)\n    }\n\n    pub fn with_target(mut self, target: Target) -> Self {\n        self.target = target;\n        self\n    }\n\n    #[deprecated(note = \"`color` is replaced by `display`; see `Options` docs for more details\")]\n    pub fn with_color(mut self, color: bool) -> Self {\n        self.color = color;\n        self\n    }\n\n    pub fn with_display(mut self, display: DisplayOptions) -> Self {\n        self.display = display;\n        self\n    }\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, strum::EnumString)]\n#[strum(serialize_all = \"snake_case\")]\n#[non_exhaustive]\npub enum DisplayOptions {\n    /// Plain text\n    Plain,\n    /// With ANSI colors\n    AnsiColor,\n}\n\n#[doc = include_str!(\"../README.md\")]\n#[cfg(doctest)]\npub struct ReadmeDoctests;\n\n/// Lex PRQL source into Lexer Representation.\npub fn prql_to_tokens(prql: &str) -> Result<lr::Tokens, ErrorMessages> {\n    prqlc_parser::lexer::lex_source(prql).map_err(|e| {\n        e.into_iter()\n            .map(|e| e.into())\n            .collect::<Vec<ErrorMessage>>()\n            .into()\n    })\n}\n\n/// Parse PRQL into a PL AST\n// TODO: rename this to `prql_to_pl_simple`\npub fn prql_to_pl(prql: &str) -> Result<pr::ModuleDef, ErrorMessages> {\n    let source_tree = SourceTree::from(prql);\n    prql_to_pl_tree(&source_tree)\n}\n\n/// Parse PRQL into a PL AST\npub fn prql_to_pl_tree(prql: &SourceTree) -> Result<pr::ModuleDef, ErrorMessages> {\n    parser::parse(prql).map_err(|e| ErrorMessages::from(e).composed(prql))\n}\n\n/// Perform semantic analysis and convert PL to RQ.\n// TODO: rename this to `pl_to_rq_simple`\npub fn pl_to_rq(pl: pr::ModuleDef) -> Result<ir::rq::RelationalQuery, ErrorMessages> {\n    semantic::resolve_and_lower(pl, &[], None)\n        .map_err(|e| e.with_source(ErrorSource::NameResolver).into())\n}\n\n/// Perform semantic analysis and convert PL to RQ.\npub fn pl_to_rq_tree(\n    pl: pr::ModuleDef,\n    main_path: &[String],\n    database_module_path: &[String],\n) -> Result<ir::rq::RelationalQuery, ErrorMessages> {\n    semantic::resolve_and_lower(pl, main_path, Some(database_module_path))\n        .map_err(|e| e.with_source(ErrorSource::NameResolver).into())\n}\n\n/// Generate SQL from RQ.\npub fn rq_to_sql(rq: ir::rq::RelationalQuery, options: &Options) -> Result<String, ErrorMessages> {\n    sql::compile(rq, options).map_err(|e| e.with_source(ErrorSource::SQL).into())\n}\n\n/// Generate PRQL code from PL AST\npub fn pl_to_prql(pl: &pr::ModuleDef) -> Result<String, ErrorMessages> {\n    Ok(codegen::WriteSource::write(&pl.stmts, codegen::WriteOpt::default()).unwrap())\n}\n\n/// JSON serialization and deserialization functions\npub mod json {\n    use super::*;\n\n    /// JSON serialization\n    pub fn from_pl(pl: &pr::ModuleDef) -> Result<String, ErrorMessages> {\n        serde_json::to_string(pl).map_err(convert_json_err)\n    }\n\n    /// JSON deserialization\n    pub fn to_pl(json: &str) -> Result<pr::ModuleDef, ErrorMessages> {\n        serde_json::from_str(json).map_err(convert_json_err)\n    }\n\n    /// JSON serialization\n    pub fn from_rq(rq: &ir::rq::RelationalQuery) -> Result<String, ErrorMessages> {\n        serde_json::to_string(rq).map_err(convert_json_err)\n    }\n\n    /// JSON deserialization\n    pub fn to_rq(json: &str) -> Result<ir::rq::RelationalQuery, ErrorMessages> {\n        serde_json::from_str(json).map_err(convert_json_err)\n    }\n\n    fn convert_json_err(err: serde_json::Error) -> ErrorMessages {\n        ErrorMessages::from(Error::new_simple(err.to_string()))\n    }\n}\n\n/// All paths are relative to the project root.\n// We use `SourceTree` to represent both a single file (including a \"file\" piped\n// from stdin), and a collection of files. (Possibly this could be implemented\n// as a Trait with a Struct for each type, which would use structure over values\n// (i.e. `Option<PathBuf>` below signifies whether it's a project or not). But\n// waiting until it's necessary before splitting it out.)\n#[derive(Debug, Clone, Default, Serialize)]\npub struct SourceTree {\n    /// Path to the root of the source tree.\n    pub root: Option<PathBuf>,\n\n    /// Mapping from file paths into into their contents.\n    /// Paths are relative to the root.\n    pub sources: HashMap<PathBuf, String>,\n\n    /// Index of source ids to paths. Used to keep [error::Span] lean.\n    source_ids: HashMap<u16, PathBuf>,\n}\n\nimpl SourceTree {\n    pub fn single(path: PathBuf, content: String) -> Self {\n        SourceTree {\n            sources: [(path.clone(), content)].into(),\n            source_ids: [(1, path)].into(),\n            root: None,\n        }\n    }\n\n    pub fn new<I>(iter: I, root: Option<PathBuf>) -> Self\n    where\n        I: IntoIterator<Item = (PathBuf, String)>,\n    {\n        let mut res = SourceTree {\n            sources: HashMap::new(),\n            source_ids: HashMap::new(),\n            root,\n        };\n\n        for (index, (path, content)) in iter.into_iter().enumerate() {\n            res.sources.insert(path.clone(), content);\n            res.source_ids.insert((index + 1) as u16, path);\n        }\n        res\n    }\n\n    pub fn insert(&mut self, path: PathBuf, content: String) {\n        let last_id = self.source_ids.keys().max().cloned().unwrap_or(0);\n        self.sources.insert(path.clone(), content);\n        self.source_ids.insert(last_id + 1, path);\n    }\n\n    pub fn get_path(&self, source_id: u16) -> Option<&PathBuf> {\n        self.source_ids.get(&source_id)\n    }\n}\n\nimpl<S: ToString> From<S> for SourceTree {\n    fn from(source: S) -> Self {\n        SourceTree::single(PathBuf::from(\"\"), source.to_string())\n    }\n}\n\n/// Debugging and unstable API functions\npub mod internal {\n    use super::*;\n\n    /// Create column-level lineage graph\n    pub fn pl_to_lineage(\n        pl: pr::ModuleDef,\n    ) -> Result<semantic::reporting::FrameCollector, ErrorMessages> {\n        let ast = Some(pl.clone());\n\n        let root_module = semantic::resolve(pl).map_err(ErrorMessages::from)?;\n\n        let (main, _) = root_module.find_main_rel(&[]).unwrap();\n        let mut fc =\n            semantic::reporting::collect_frames(*main.clone().into_relation_var().unwrap());\n        fc.ast = ast;\n\n        Ok(fc)\n    }\n\n    pub mod json {\n        use super::*;\n\n        /// JSON serialization of FrameCollector lineage\n        pub fn from_lineage(\n            fc: &semantic::reporting::FrameCollector,\n        ) -> Result<String, ErrorMessages> {\n            serde_json::to_string(fc).map_err(convert_json_err)\n        }\n\n        fn convert_json_err(err: serde_json::Error) -> ErrorMessages {\n            ErrorMessages::from(Error::new_simple(err.to_string()))\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::str::FromStr;\n\n    use insta::assert_debug_snapshot;\n\n    use crate::pr::Ident;\n    use crate::Target;\n\n    pub fn compile(prql: &str) -> Result<String, super::ErrorMessages> {\n        anstream::ColorChoice::Never.write_global();\n        super::compile(prql, &super::Options::default().no_signature())\n    }\n\n    #[test]\n    fn test_starts_with() {\n        // Over-testing, from co-pilot, can remove some of them.\n        let a = Ident::from_path(vec![\"a\", \"b\", \"c\"]);\n        let b = Ident::from_path(vec![\"a\", \"b\"]);\n        let c = Ident::from_path(vec![\"a\", \"b\", \"c\", \"d\"]);\n        let d = Ident::from_path(vec![\"a\", \"b\", \"d\"]);\n        let e = Ident::from_path(vec![\"a\", \"c\"]);\n        let f = Ident::from_path(vec![\"b\", \"c\"]);\n        assert!(a.starts_with(&b));\n        assert!(a.starts_with(&a));\n        assert!(!a.starts_with(&c));\n        assert!(!a.starts_with(&d));\n        assert!(!a.starts_with(&e));\n        assert!(!a.starts_with(&f));\n    }\n\n    #[test]\n    fn test_target_from_str() {\n        assert_debug_snapshot!(Target::from_str(\"sql.postgres\"), @r\"\n        Ok(\n            Sql(\n                Some(\n                    Postgres,\n                ),\n            ),\n        )\n        \");\n\n        assert_debug_snapshot!(Target::from_str(\"sql.poostgres\"), @r#\"\n        Err(\n            Error {\n                kind: Error,\n                span: None,\n                reason: NotFound {\n                    name: \"\\\"sql.poostgres\\\"\",\n                    namespace: \"target\",\n                },\n                hints: [],\n                code: None,\n            },\n        )\n        \"#);\n\n        assert_debug_snapshot!(Target::from_str(\"postgres\"), @r#\"\n        Err(\n            Error {\n                kind: Error,\n                span: None,\n                reason: NotFound {\n                    name: \"\\\"postgres\\\"\",\n                    namespace: \"target\",\n                },\n                hints: [],\n                code: None,\n            },\n        )\n        \"#);\n    }\n\n    /// Confirm that all target names can be parsed.\n    #[test]\n    fn test_target_names() {\n        let _: Vec<_> = Target::names()\n            .into_iter()\n            .map(|name| Target::from_str(&name))\n            .collect();\n    }\n\n    /// Regression test for #4633: sort inside group should not leak to outer query after join.\n    ///\n    /// Per PRQL spec, `group` resets the order. The `sort` inside a group is for\n    /// row selection (which row to keep), not output ordering. After the group,\n    /// there is no defined order, so it should not appear in the outer query.\n    #[test]\n    fn test_sort_not_propagated_after_join() {\n        use insta::assert_snapshot;\n\n        // DISTINCT ON (postgres) uses the sort for row selection within the CTE.\n        // This internal sorting must not leak to the outer query after a join.\n        assert_snapshot!(\n            super::compile(\n                r#\"\n                prql target:sql.postgres\n\n                from tracks\n                group media_type_id (\n                    sort name\n                    take 1\n                )\n                join media_types (== media_type_id)\n                select {\n                    tracks.track_id,\n                    media_types.name\n                }\n                \"#,\n                &super::Options::default().no_signature()\n            ).unwrap(),\n            @\"\n        WITH table_0 AS (\n          SELECT\n            DISTINCT ON (media_type_id) track_id,\n            media_type_id,\n            name\n          FROM\n            tracks\n          ORDER BY\n            media_type_id,\n            name\n        )\n        SELECT\n          table_0.track_id,\n          media_types.name\n        FROM\n          table_0\n          INNER JOIN media_types ON table_0.media_type_id = media_types.media_type_id\n        \"\n        );\n    }\n\n    /// Verify that explicit sorts after group are preserved past joins.\n    ///\n    /// Per PRQL spec, `sort` introduces a new order. When the user explicitly\n    /// sorts AFTER a group, that becomes the new output order and should\n    /// propagate through subsequent transforms including joins.\n    #[test]\n    fn test_explicit_sort_after_distinct_on_preserved() {\n        use insta::assert_snapshot;\n\n        // Explicit `sort media_type_id` after the group introduces a new order.\n        // This user-requested ordering should propagate past the join.\n        assert_snapshot!(\n            super::compile(\n                r#\"\n                prql target:sql.postgres\n\n                from tracks\n                group media_type_id (\n                    sort name\n                    take 1\n                )\n                sort media_type_id\n                join media_types (== media_type_id)\n                select {\n                    tracks.track_id,\n                    media_types.name\n                }\n                \"#,\n                &super::Options::default().no_signature()\n            ).unwrap(),\n            @\"\n        WITH table_0 AS (\n          SELECT\n            DISTINCT ON (media_type_id) track_id,\n            media_type_id,\n            name\n          FROM\n            tracks\n          ORDER BY\n            media_type_id,\n            name\n        ),\n        table_1 AS (\n          SELECT\n            table_0.track_id,\n            media_types.name,\n            table_0.media_type_id\n          FROM\n            table_0\n            INNER JOIN media_types ON table_0.media_type_id = media_types.media_type_id\n        )\n        SELECT\n          track_id,\n          name\n        FROM\n          table_1\n        ORDER BY\n          media_type_id\n        \"\n        );\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/main.rs",
    "content": "#[cfg(all(not(target_family = \"wasm\"), feature = \"cli\"))]\nmod cli;\n\n#[cfg(all(not(target_family = \"wasm\"), feature = \"cli\"))]\nfn main() -> color_eyre::eyre::Result<()> {\n    // Use a larger stack size (8 MiB) to avoid stack overflows on Windows,\n    // where the default stack is only 1 MiB.\n    const STACK_SIZE: usize = 8 * 1024 * 1024;\n\n    let thread = std::thread::Builder::new()\n        .stack_size(STACK_SIZE)\n        .spawn(cli::main)\n        .expect(\"failed to spawn main thread\");\n\n    thread.join().expect(\"main thread panicked\")?;\n    Ok(())\n}\n\n#[cfg(any(target_family = \"wasm\", not(feature = \"cli\")))]\nfn main() {\n    panic!(\"Crate is not built with the `cli` feature enabled, or was built for a wasm target.\");\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/parser.rs",
    "content": "use std::path::PathBuf;\nuse std::{collections::HashMap, path::Path};\n\nuse itertools::Itertools;\n\nuse crate::debug;\nuse crate::lr;\nuse crate::pr;\nuse crate::{Error, Errors, Result, SourceTree, WithErrorInfo};\n\npub fn parse(file_tree: &SourceTree) -> Result<pr::ModuleDef, Errors> {\n    // register a new stage of the compiler\n    // (here should register lexer stage first, but that all happens in a single call to prqlc_parser)\n    debug::log_entry(|| debug::DebugEntryKind::ReprPrql(file_tree.clone()));\n    debug::log_stage(debug::Stage::Parsing);\n\n    let source_files = linearize_tree(file_tree)?;\n\n    // reverse the id->file_path map\n    let ids: HashMap<_, _> = file_tree\n        .source_ids\n        .iter()\n        .map(|(a, b)| (b.as_path(), a))\n        .collect();\n\n    // init the root module def\n    let mut root = pr::ModuleDef {\n        name: \"Project\".to_string(),\n        stmts: Vec::new(),\n    };\n\n    // parse and insert into the root\n    let mut errors = Vec::new();\n    for source_file in source_files {\n        let id = ids\n            .get(&source_file.file_path)\n            .map(|x| **x)\n            .expect(\"source tree has malformed ids\");\n\n        match parse_source(source_file.content, id) {\n            Ok(stmts) => {\n                insert_stmts_at_path(&mut root, source_file.module_path, stmts);\n            }\n            Err(errs) => errors.extend(errs),\n        }\n    }\n    if errors.is_empty() {\n        debug::log_entry(|| debug::DebugEntryKind::ReprPr(root.clone()));\n        Ok(root)\n    } else {\n        Err(Errors(errors))\n    }\n}\n\n/// Build PR AST from a PRQL query string.\n// We have this function in `prqlc` rather than in `prqlc-parser` crate since\n// our logging is in `prqlc` and we want to log the LR. (We could split the logging\n// out into a separate crate, but it has dependencies on `prqlc` internals and\n// would be an effort)\npub(crate) fn parse_source(source: &str, source_id: u16) -> Result<Vec<pr::Stmt>, Vec<Error>> {\n    let (tokens, mut errors) = prqlc_parser::lexer::lex_source_recovery(source, source_id);\n\n    let ast = if let Some(tokens) = tokens {\n        debug::log_entry(|| debug::DebugEntryKind::ReprLr(lr::Tokens(tokens.clone())));\n\n        let (ast, parse_errors) = prqlc_parser::parser::parse_lr_to_pr(source_id, tokens);\n        errors.extend(parse_errors);\n        ast\n    } else {\n        None\n    };\n\n    if errors.is_empty() {\n        Ok(ast.unwrap_or_default())\n    } else {\n        Err(errors)\n    }\n}\n\nstruct SourceFile<'a> {\n    file_path: &'a Path,\n    module_path: Vec<String>,\n    content: &'a str,\n}\n\nfn linearize_tree(tree: &SourceTree) -> Result<Vec<SourceFile<'_>>> {\n    // find root\n    let root_path;\n\n    if tree.sources.len() == 1 {\n        // if there is only one file, use that as the root\n        root_path = tree.sources.keys().next().unwrap();\n    } else if let Some(root) = tree.sources.get_key_value(&PathBuf::from(\"\")) {\n        // if there is an empty path, that's the root\n        root_path = root.0;\n    } else if let Some(root) = tree.sources.keys().find(path_starts_with_uppercase) {\n        root_path = root;\n    } else {\n        if tree.sources.is_empty() {\n            // TODO: should we allow non `.prql` files? We could require `.prql`\n            // for modules but then allow any file if a single file is passed\n            // (python allows this, for example)\n            return Err(Error::new_simple(\n                \"No `.prql` files found in the source tree\",\n            ));\n        }\n\n        let file_names = tree\n            .sources\n            .keys()\n            .map(|p| format!(\" - {}\", p.to_str().unwrap_or_default()))\n            .sorted()\n            .join(\"\\n\");\n\n        return Err(Error::new_simple(format!(\n            \"Cannot find the root module within the following files:\\n{file_names}\"\n        ))\n        .push_hint(\"add a file that starts with uppercase letter to the root directory\")\n        .with_code(\"E0002\"));\n    }\n\n    let mut sources: Vec<_> = Vec::with_capacity(tree.sources.len());\n\n    // prepare paths\n    for (path, source) in &tree.sources {\n        if path == root_path {\n            continue;\n        }\n\n        let module_path = os_path_to_prql_path(path)?;\n\n        sources.push(SourceFile {\n            file_path: path,\n            module_path,\n            content: source,\n        });\n    }\n\n    // sort to make this deterministic\n    sources.sort_by(|a, b| a.module_path.cmp(&b.module_path));\n\n    // add root\n    let root_content = tree.sources.get(root_path).unwrap();\n    sources.push(SourceFile {\n        file_path: root_path,\n        module_path: Vec::new(),\n        content: root_content,\n    });\n\n    Ok(sources)\n}\n\nfn insert_stmts_at_path(module: &mut pr::ModuleDef, mut path: Vec<String>, stmts: Vec<pr::Stmt>) {\n    if path.is_empty() {\n        module.stmts.extend(stmts);\n        return;\n    }\n\n    let step = path.remove(0);\n\n    // find submodule def\n    let submodule = module.stmts.iter_mut().find(|x| is_mod_def_for(x, &step));\n    let submodule = if let Some(sm) = submodule {\n        sm\n    } else {\n        // insert new module def\n        let new_stmt = pr::Stmt::new(pr::StmtKind::ModuleDef(pr::ModuleDef {\n            name: step,\n            stmts: Vec::new(),\n        }));\n        module.stmts.push(new_stmt);\n        module.stmts.last_mut().unwrap()\n    };\n    let submodule = submodule.kind.as_module_def_mut().unwrap();\n\n    insert_stmts_at_path(submodule, path, stmts);\n}\n\npub(crate) fn is_mod_def_for(stmt: &pr::Stmt, name: &str) -> bool {\n    stmt.kind.as_module_def().is_some_and(|x| x.name == name)\n}\n\nfn path_starts_with_uppercase(p: &&PathBuf) -> bool {\n    p.components()\n        .next()\n        .and_then(|x| x.as_os_str().to_str())\n        .and_then(|x| x.chars().next())\n        .is_some_and(|x| x.is_uppercase())\n}\n\npub fn os_path_to_prql_path(path: &Path) -> Result<Vec<String>> {\n    // remove file format extension\n    let path = path.with_extension(\"\");\n\n    // split by /\n    path.components()\n        .map(|x| {\n            x.as_os_str()\n                .to_str()\n                .map(str::to_string)\n                .ok_or_else(|| Error::new_simple(format!(\"Invalid file path: {path:?}\")))\n        })\n        .try_collect()\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/ast_expand.rs",
    "content": "use std::collections::HashMap;\n\nuse itertools::Itertools;\nuse prqlc_parser::generic;\n\nuse crate::ir::decl;\nuse crate::ir::pl::{self, new_binop};\nuse crate::pr;\nuse crate::semantic::{NS_THAT, NS_THIS};\nuse crate::{Error, Result};\n\n/// An AST pass that maps AST to PL.\npub fn expand_expr(expr: pr::Expr) -> Result<pl::Expr> {\n    let kind = match expr.kind {\n        pr::ExprKind::Ident(v) => pl::ExprKind::Ident(v),\n        pr::ExprKind::Literal(v) => pl::ExprKind::Literal(v),\n        pr::ExprKind::Pipeline(v) => {\n            let mut e = desugar_pipeline(v)?;\n            e.alias = expr.alias.or(e.alias);\n            return Ok(e);\n        }\n        pr::ExprKind::Tuple(v) => pl::ExprKind::Tuple(expand_exprs(v)?),\n        pr::ExprKind::Array(v) => pl::ExprKind::Array(expand_exprs(v)?),\n\n        pr::ExprKind::Range(v) => expands_range(v)?,\n\n        pr::ExprKind::Unary(unary) => expand_unary(unary)?,\n        pr::ExprKind::Binary(binary) => expand_binary(binary)?,\n\n        pr::ExprKind::FuncCall(v) => pl::ExprKind::FuncCall(pl::FuncCall {\n            name: expand_expr_box(v.name)?,\n            args: expand_exprs(v.args)?,\n            named_args: v\n                .named_args\n                .into_iter()\n                .map(|(k, v)| -> Result<_> { Ok((k, expand_expr(v)?)) })\n                .try_collect()?,\n        }),\n        pr::ExprKind::Func(v) => pl::ExprKind::Func(\n            pl::Func {\n                return_ty: v.return_ty,\n                body: expand_expr_box(v.body)?,\n                params: expand_func_params(v.params)?,\n                named_params: expand_func_params(v.named_params)?,\n                name_hint: None,\n                args: Vec::new(),\n                env: HashMap::new(),\n            }\n            .into(),\n        ),\n        pr::ExprKind::SString(v) => pl::ExprKind::SString(\n            v.into_iter()\n                .map(|v| v.try_map(expand_expr))\n                .try_collect()?,\n        ),\n        pr::ExprKind::FString(v) => pl::ExprKind::FString(\n            v.into_iter()\n                .map(|v| v.try_map(expand_expr))\n                .try_collect()?,\n        ),\n        pr::ExprKind::Case(v) => pl::ExprKind::Case(\n            v.into_iter()\n                .map(|case| -> Result<_> {\n                    Ok(pl::SwitchCase {\n                        condition: expand_expr_box(case.condition)?,\n                        value: expand_expr_box(case.value)?,\n                    })\n                })\n                .try_collect()?,\n        ),\n        pr::ExprKind::Param(v) => pl::ExprKind::Param(v),\n        pr::ExprKind::Internal(v) => pl::ExprKind::Internal(v),\n    };\n\n    Ok(pl::Expr {\n        kind,\n        span: expr.span,\n        alias: expr.alias,\n        id: None,\n        target_id: None,\n        ty: None,\n        lineage: None,\n        needs_window: false,\n        flatten: false,\n    })\n}\n\n/// De-sugars range `a..b` into `{start=a, end=b}`. Open bounds are mapped into `null`.\nfn expands_range(v: generic::Range<Box<pr::Expr>>) -> Result<pl::ExprKind> {\n    let mut start = v\n        .start\n        .map(|e| expand_expr(*e))\n        .transpose()?\n        .unwrap_or_else(|| pl::Expr::new(pr::Literal::Null));\n    start.alias = Some(\"start\".into());\n    let mut end = v\n        .end\n        .map(|e| expand_expr(*e))\n        .transpose()?\n        .unwrap_or_else(|| pl::Expr::new(pr::Literal::Null));\n    end.alias = Some(\"end\".into());\n    Ok(pl::ExprKind::Tuple(vec![start, end]))\n}\n\nfn expand_exprs(exprs: Vec<pr::Expr>) -> Result<Vec<pl::Expr>> {\n    exprs.into_iter().map(expand_expr).collect()\n}\n\n#[allow(clippy::boxed_local)]\nfn expand_expr_box(expr: Box<pr::Expr>) -> Result<Box<pl::Expr>> {\n    Ok(Box::new(expand_expr(*expr)?))\n}\n\nfn desugar_pipeline(mut pipeline: pr::Pipeline) -> Result<pl::Expr> {\n    let value = pipeline.exprs.remove(0);\n    let mut value = expand_expr(value)?;\n\n    for expr in pipeline.exprs {\n        let expr = expand_expr(expr)?;\n        let span = expr.span;\n\n        value = pl::Expr::new(pl::ExprKind::FuncCall(pl::FuncCall::new_simple(\n            expr,\n            vec![value],\n        )));\n        value.span = span;\n    }\n\n    Ok(value)\n}\n\n/// Desugar unary operators into function calls.\nfn expand_unary(pr::UnaryExpr { op, expr }: pr::UnaryExpr) -> Result<pl::ExprKind> {\n    use pr::UnOp::*;\n\n    let expr = expand_expr(*expr)?;\n\n    let func_name = match op {\n        Neg => [\"std\", \"neg\"],\n        Not => [\"std\", \"not\"],\n        Add => return Ok(expr.kind),\n        EqSelf => {\n            let pl::ExprKind::Ident(ident) = expr.kind else {\n                return Err(Error::new_simple(\n                    \"self-equality operator requires a column name\",\n                ));\n            };\n            if !ident.path.is_empty() {\n                return Err(Error::new_simple(\n                    \"self-equality operator does not support namespace prefix\",\n                ));\n            }\n\n            let left = pl::Expr {\n                span: expr.span,\n                ..pl::Expr::new(pr::Ident {\n                    path: vec![NS_THIS.to_string()],\n                    name: ident.name.clone(),\n                })\n            };\n            let right = pl::Expr {\n                span: expr.span,\n                ..pl::Expr::new(pr::Ident {\n                    path: vec![NS_THAT.to_string()],\n                    name: ident.name,\n                })\n            };\n            return Ok(new_binop(left, &[\"std\", \"eq\"], right).kind);\n        }\n    };\n    Ok(pl::ExprKind::FuncCall(pl::FuncCall::new_simple(\n        pl::Expr::new(pr::Ident::from_path(func_name.to_vec())),\n        vec![expr],\n    )))\n}\n\n/// Desugar binary operators into function calls.\nfn expand_binary(pr::BinaryExpr { op, left, right }: pr::BinaryExpr) -> Result<pl::ExprKind> {\n    let left = expand_expr(*left)?;\n    let right = expand_expr(*right)?;\n\n    let func_name: Vec<&str> = match op {\n        pr::BinOp::Mul => vec![\"std\", \"mul\"],\n        pr::BinOp::DivInt => vec![\"std\", \"div_i\"],\n        pr::BinOp::DivFloat => vec![\"std\", \"div_f\"],\n        pr::BinOp::Mod => vec![\"std\", \"mod\"],\n        pr::BinOp::Pow => vec![\"std\", \"math\", \"pow\"],\n        pr::BinOp::Add => vec![\"std\", \"add\"],\n        pr::BinOp::Sub => vec![\"std\", \"sub\"],\n        pr::BinOp::Eq => vec![\"std\", \"eq\"],\n        pr::BinOp::Ne => vec![\"std\", \"ne\"],\n        pr::BinOp::Gt => vec![\"std\", \"gt\"],\n        pr::BinOp::Lt => vec![\"std\", \"lt\"],\n        pr::BinOp::Gte => vec![\"std\", \"gte\"],\n        pr::BinOp::Lte => vec![\"std\", \"lte\"],\n        pr::BinOp::RegexSearch => vec![\"std\", \"regex_search\"],\n        pr::BinOp::And => vec![\"std\", \"and\"],\n        pr::BinOp::Or => vec![\"std\", \"or\"],\n        pr::BinOp::Coalesce => vec![\"std\", \"coalesce\"],\n    };\n\n    // For the power operator, we need to reverse the order, since `math.pow a\n    // b` is equivalent to `b ** a`. (but for example `sub a b` is equivalent to\n    // `a - b`).\n    //\n    // (I think this is the most globally consistent approach, since final\n    // arguments should be the \"data\", which in the case of `pow` would be the\n    // base; but it's not perfect, we could change it...)\n    let (left, right) = match op {\n        pr::BinOp::Pow => (right, left),\n        _ => (left, right),\n    };\n    Ok(new_binop(left, &func_name, right).kind)\n}\n\nfn expand_func_param(value: pr::FuncParam) -> Result<pl::FuncParam> {\n    Ok(pl::FuncParam {\n        name: value.name,\n        ty: value.ty,\n        default_value: value.default_value.map(expand_expr_box).transpose()?,\n    })\n}\n\nfn expand_func_params(value: Vec<pr::FuncParam>) -> Result<Vec<pl::FuncParam>> {\n    value.into_iter().map(expand_func_param).collect()\n}\n\nfn expand_stmt(value: pr::Stmt) -> Result<pl::Stmt> {\n    Ok(pl::Stmt {\n        id: None,\n        kind: expand_stmt_kind(value.kind)?,\n        span: value.span,\n        annotations: value\n            .annotations\n            .into_iter()\n            .map(expand_annotation)\n            .try_collect()?,\n    })\n}\n\npub fn expand_module_def(v: pr::ModuleDef) -> Result<pl::ModuleDef> {\n    Ok(pl::ModuleDef {\n        name: v.name,\n        stmts: expand_stmts(v.stmts)?,\n    })\n}\n\nfn expand_stmts(value: Vec<pr::Stmt>) -> Result<Vec<pl::Stmt>> {\n    value.into_iter().map(expand_stmt).collect()\n}\n\nfn expand_stmt_kind(value: pr::StmtKind) -> Result<pl::StmtKind> {\n    Ok(match value {\n        pr::StmtKind::QueryDef(v) => pl::StmtKind::QueryDef(v),\n        pr::StmtKind::VarDef(v) => pl::StmtKind::VarDef(pl::VarDef {\n            name: v.name,\n            value: v.value.map(expand_expr_box).transpose()?,\n            ty: v.ty,\n        }),\n        pr::StmtKind::TypeDef(v) => pl::StmtKind::TypeDef(pl::TypeDef {\n            name: v.name,\n            value: v.value,\n        }),\n        pr::StmtKind::ModuleDef(v) => pl::StmtKind::ModuleDef(expand_module_def(v)?),\n        pr::StmtKind::ImportDef(v) => pl::StmtKind::ImportDef(pl::ImportDef {\n            alias: v.alias,\n            name: v.name,\n        }),\n    })\n}\n\nfn expand_annotation(value: pr::Annotation) -> Result<pl::Annotation> {\n    Ok(pl::Annotation {\n        expr: expand_expr_box(value.expr)?,\n    })\n}\n\n/// An AST pass that tries to revert the mapping from AST to PL\npub fn restrict_expr(expr: pl::Expr) -> pr::Expr {\n    pr::Expr {\n        kind: restrict_expr_kind(expr.kind),\n        span: expr.span,\n        alias: expr.alias,\n        doc_comment: None,\n    }\n}\n\n#[allow(clippy::boxed_local)]\nfn restrict_expr_box(expr: Box<pl::Expr>) -> Box<pr::Expr> {\n    Box::new(restrict_expr(*expr))\n}\n\nfn restrict_exprs(exprs: Vec<pl::Expr>) -> Vec<pr::Expr> {\n    exprs.into_iter().map(restrict_expr).collect()\n}\n\nfn restrict_expr_kind(value: pl::ExprKind) -> pr::ExprKind {\n    match value {\n        pl::ExprKind::Ident(v) => pr::ExprKind::Ident(v),\n        pl::ExprKind::Literal(v) => pr::ExprKind::Literal(v),\n        pl::ExprKind::Tuple(v) => pr::ExprKind::Tuple(restrict_exprs(v)),\n        pl::ExprKind::Array(v) => pr::ExprKind::Array(restrict_exprs(v)),\n        pl::ExprKind::FuncCall(v) => pr::ExprKind::FuncCall(pr::FuncCall {\n            name: restrict_expr_box(v.name),\n            args: restrict_exprs(v.args),\n            named_args: v\n                .named_args\n                .into_iter()\n                .map(|(k, v)| (k, restrict_expr(v)))\n                .collect(),\n        }),\n        pl::ExprKind::Func(v) => {\n            let func = pr::ExprKind::Func(\n                pr::Func {\n                    return_ty: v.return_ty,\n                    body: restrict_expr_box(v.body),\n                    params: restrict_func_params(v.params),\n                    named_params: restrict_func_params(v.named_params),\n                }\n                .into(),\n            );\n            if v.args.is_empty() {\n                func\n            } else {\n                pr::ExprKind::FuncCall(pr::FuncCall {\n                    name: Box::new(pr::Expr::new(func)),\n                    args: restrict_exprs(v.args),\n                    named_args: Default::default(),\n                })\n            }\n        }\n        pl::ExprKind::SString(v) => {\n            pr::ExprKind::SString(v.into_iter().map(|v| v.map(restrict_expr)).collect())\n        }\n        pl::ExprKind::FString(v) => {\n            pr::ExprKind::FString(v.into_iter().map(|v| v.map(restrict_expr)).collect())\n        }\n        pl::ExprKind::Case(v) => pr::ExprKind::Case(\n            v.into_iter()\n                .map(|case| pr::SwitchCase {\n                    condition: restrict_expr_box(case.condition),\n                    value: restrict_expr_box(case.value),\n                })\n                .collect(),\n        ),\n        pl::ExprKind::Param(v) => pr::ExprKind::Param(v),\n        pl::ExprKind::Internal(v) => pr::ExprKind::Internal(v),\n\n        // TODO: these are not correct, they are producing invalid PRQL\n        pl::ExprKind::All { within, .. } => restrict_expr(*within).kind,\n        pl::ExprKind::TransformCall(tc) => pr::ExprKind::Ident(pr::Ident::from_name(format!(\n            \"({} ...)\",\n            tc.kind.as_ref().as_ref()\n        ))),\n        pl::ExprKind::RqOperator { name, .. } => {\n            pr::ExprKind::Ident(pr::Ident::from_name(format!(\"({name} ...)\")))\n        }\n    }\n}\n\nfn restrict_func_params(value: Vec<pl::FuncParam>) -> Vec<pr::FuncParam> {\n    value.into_iter().map(restrict_func_param).collect()\n}\n\nfn restrict_func_param(value: pl::FuncParam) -> pr::FuncParam {\n    pr::FuncParam {\n        name: value.name,\n        ty: value.ty,\n        default_value: value.default_value.map(restrict_expr_box),\n    }\n}\n\n/// Restricts a tuple of form `{start=a, end=b}` into a range `a..b`.\npub fn try_restrict_range(expr: pl::Expr) -> Result<(pl::Expr, pl::Expr), pl::Expr> {\n    let pl::ExprKind::Tuple(fields) = expr.kind else {\n        return Err(expr);\n    };\n\n    if fields.len() != 2\n        || fields[0].alias.as_deref() != Some(\"start\")\n        || fields[1].alias.as_deref() != Some(\"end\")\n    {\n        return Err(pl::Expr {\n            kind: pl::ExprKind::Tuple(fields),\n            ..expr\n        });\n    }\n\n    let [start, end]: [pl::Expr; 2] = fields.try_into().unwrap();\n\n    Ok((start, end))\n}\n\n/// Returns None if the Expr is a null literal and Some(expr) otherwise.\npub fn restrict_null_literal(expr: pl::Expr) -> Option<pl::Expr> {\n    if let pl::ExprKind::Literal(pr::Literal::Null) = expr.kind {\n        None\n    } else {\n        Some(expr)\n    }\n}\n\npub fn restrict_module_def(def: pl::ModuleDef) -> pr::ModuleDef {\n    pr::ModuleDef {\n        name: def.name,\n        stmts: restrict_stmts(def.stmts),\n    }\n}\n\nfn restrict_stmts(stmts: Vec<pl::Stmt>) -> Vec<pr::Stmt> {\n    stmts.into_iter().map(restrict_stmt).collect()\n}\n\nfn restrict_stmt(stmt: pl::Stmt) -> pr::Stmt {\n    let kind = match stmt.kind {\n        pl::StmtKind::QueryDef(def) => pr::StmtKind::QueryDef(def),\n        pl::StmtKind::VarDef(def) => pr::StmtKind::VarDef(pr::VarDef {\n            kind: pr::VarDefKind::Let,\n            name: def.name,\n            value: def.value.map(restrict_expr_box),\n            ty: def.ty,\n        }),\n        pl::StmtKind::TypeDef(def) => pr::StmtKind::TypeDef(pr::TypeDef {\n            name: def.name,\n            value: def.value,\n        }),\n        pl::StmtKind::ModuleDef(def) => pr::StmtKind::ModuleDef(restrict_module_def(def)),\n        pl::StmtKind::ImportDef(def) => pr::StmtKind::ImportDef(pr::ImportDef {\n            name: def.name,\n            alias: def.alias,\n        }),\n    };\n\n    pr::Stmt {\n        kind,\n        span: stmt.span,\n        annotations: stmt\n            .annotations\n            .into_iter()\n            .map(restrict_annotation)\n            .collect(),\n        doc_comment: None,\n    }\n}\n\npub fn restrict_annotation(value: pl::Annotation) -> pr::Annotation {\n    pr::Annotation {\n        expr: restrict_expr_box(value.expr),\n    }\n}\n\npub fn restrict_module(value: decl::Module) -> pr::ModuleDef {\n    let mut stmts = Vec::new();\n    for (name, decl) in value.names.into_iter().sorted_by_key(|x| x.0.clone()) {\n        stmts.extend(restrict_decl(name, decl))\n    }\n\n    pr::ModuleDef {\n        name: \"\".to_string(),\n        stmts,\n    }\n}\n\nfn restrict_decl(name: String, value: decl::Decl) -> Option<pr::Stmt> {\n    let kind = match value.kind {\n        decl::DeclKind::Module(module) => pr::StmtKind::ModuleDef(pr::ModuleDef {\n            name,\n            stmts: restrict_module(module).stmts,\n        }),\n        decl::DeclKind::LayeredModules(mut stack) => {\n            let module = stack.pop()?;\n\n            pr::StmtKind::ModuleDef(pr::ModuleDef {\n                name,\n                stmts: restrict_module(module).stmts,\n            })\n        }\n        decl::DeclKind::TableDecl(table_decl) => pr::StmtKind::VarDef(pr::VarDef {\n            kind: pr::VarDefKind::Let,\n            name: name.clone(),\n            value: Some(Box::new(match table_decl.expr {\n                decl::TableExpr::RelationVar(expr) => restrict_expr(*expr),\n                decl::TableExpr::LocalTable => {\n                    pr::Expr::new(pr::ExprKind::Internal(\"local_table\".into()))\n                }\n                decl::TableExpr::None => {\n                    pr::Expr::new(pr::ExprKind::Internal(\"literal_tracker\".to_string()))\n                }\n                decl::TableExpr::Param(id) => pr::Expr::new(pr::ExprKind::Param(id)),\n            })),\n            ty: table_decl.ty,\n        }),\n\n        decl::DeclKind::InstanceOf(ident, _) => {\n            new_internal_stmt(name, format!(\"instance_of.{ident}\"))\n        }\n        decl::DeclKind::Column(id) => new_internal_stmt(name, format!(\"column.{id}\")),\n        decl::DeclKind::Infer(_) => new_internal_stmt(name, \"infer\".to_string()),\n\n        decl::DeclKind::Expr(mut expr) => pr::StmtKind::VarDef(pr::VarDef {\n            kind: pr::VarDefKind::Let,\n            name,\n            ty: expr.ty.take(),\n            value: Some(restrict_expr_box(expr)),\n        }),\n        decl::DeclKind::Ty(ty) => pr::StmtKind::TypeDef(pr::TypeDef { name, value: ty }),\n        decl::DeclKind::QueryDef(query_def) => pr::StmtKind::QueryDef(Box::new(query_def)),\n        decl::DeclKind::Import(ident) => pr::StmtKind::ImportDef(pr::ImportDef {\n            alias: Some(name),\n            name: ident,\n        }),\n    };\n    Some(pr::Stmt::new(kind))\n}\n\nfn new_internal_stmt(name: String, internal: String) -> pr::StmtKind {\n    pr::StmtKind::VarDef(pr::VarDef {\n        kind: pr::VarDefKind::Let,\n        name,\n        value: Some(Box::new(pr::Expr::new(pr::ExprKind::Internal(internal)))),\n        ty: None,\n    })\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/lowering.rs",
    "content": "use std::collections::hash_map::RandomState;\nuse std::collections::{BTreeSet, HashMap, HashSet};\nuse std::iter::zip;\n\nuse enum_as_inner::EnumAsInner;\nuse itertools::Itertools;\nuse prqlc_parser::generic::{InterpolateItem, Range, SwitchCase};\nuse prqlc_parser::lexer::lr::Literal;\nuse semver::{Prerelease, Version};\n\nuse crate::compiler_version;\nuse crate::ir::decl::{self, DeclKind, Module, RootModule, TableExpr};\nuse crate::ir::generic::{ColumnSort, WindowFrame};\nuse crate::ir::pl::TableExternRef::LocalTable;\nuse crate::ir::pl::{self, Ident, Lineage, LineageColumn, PlFold, QueryDef};\nuse crate::ir::rq::{\n    self, CId, RelationColumn, RelationLiteral, RelationalQuery, TId, TableDecl, Transform,\n};\nuse crate::pr::TyTupleField;\nuse crate::semantic::write_pl;\nuse crate::utils::{toposort, IdGenerator};\nuse crate::{Error, Reason, Result, Span, WithErrorInfo};\n\n/// Convert a resolved expression at path `main_path` relative to `root_mod`\n/// into RQ and make sure that:\n/// - transforms are not nested,\n/// - transforms have correct partition, window and sort set,\n/// - make sure there are no unresolved expressions.\n///\n/// All table references must reside within module at `database_module_path`.\n/// They are compiled to table identifiers, using their path relative to the database module.\n/// For example, with `database_module_path=my_database`:\n/// - `my_database.my_table` will compile to `\"my_table\"`,\n/// - `my_database.my_schema.my_table` will compile to `\"my_schema.my_table\"`,\n/// - `my_table` will error out saying that this table does not reside in current database.\npub fn lower_to_ir(\n    root_mod: RootModule,\n    main_path: &[String],\n    database_module_path: &[String],\n) -> Result<(RelationalQuery, RootModule)> {\n    // find main\n    log::debug!(\"lookup for main pipeline in {main_path:?}\");\n    let (_, main_ident) = root_mod.find_main_rel(main_path).map_err(|(_hint, span)| {\n        // Provide better error messages based on what's in the module\n        let user_declared_names: Vec<_> = root_mod\n            .module\n            .names\n            .keys()\n            .filter(|name| *name != \"std\" && *name != \"default_db\")\n            .collect();\n\n        let error = if user_declared_names.is_empty() {\n            // No user declarations - empty query or only comments\n            // Message is self-explanatory, no hint needed\n            Error::new_simple(\"No PRQL query entered\").with_code(\"E0001\")\n        } else {\n            // Has declarations but no pipeline starting with 'from'\n            Error::new_simple(\"PRQL queries must begin with 'from'\")\n                .with_code(\"E0001\")\n                .push_hint(\"A query must start with a 'from' statement to define the main pipeline\")\n        };\n\n        error.with_span(span)\n    })?;\n\n    // find & validate query def\n    let def = root_mod.find_query_def(&main_ident);\n    let def = def.cloned().unwrap_or_default();\n    validate_query_def(&def)?;\n\n    // find all tables in the root module\n    let tables = TableExtractor::extract(&root_mod.module);\n\n    // prune and toposort\n    let tables = toposort_tables(tables, &main_ident);\n\n    // lower tables\n    let mut l = Lowerer::new(root_mod, database_module_path);\n    let mut main_relation = None;\n    for (fq_ident, (table, declared_at)) in tables {\n        let is_main = fq_ident == main_ident;\n\n        l.lower_table_decl(table, fq_ident)\n            .map_err(with_span_if_not_exists(|| get_span_of_id(&l, declared_at)))?;\n\n        if is_main {\n            let main_table = l.table_buffer.pop().unwrap();\n            main_relation = Some(main_table.relation);\n        }\n    }\n\n    let query = RelationalQuery {\n        def,\n        tables: l.table_buffer,\n        relation: main_relation.unwrap(),\n    };\n    Ok((query, l.root_mod))\n}\n\nfn extern_ref_to_relation(\n    mut columns: Vec<TyTupleField>,\n    fq_ident: &Ident,\n    database_module_path: &[String],\n) -> Result<(rq::Relation, Option<String>), Error> {\n    let extern_name = if fq_ident.starts_with_path(database_module_path) {\n        let relative_to_database: Vec<&String> =\n            fq_ident.iter().skip(database_module_path.len()).collect();\n        if relative_to_database.is_empty() {\n            None\n        } else {\n            Some(Ident::from_path(relative_to_database))\n        }\n    } else {\n        None\n    };\n\n    let Some(extern_name) = extern_name else {\n        let database_module = Ident::from_path(database_module_path.to_vec());\n        return Err(Error::new_simple(\"this table is not in the current database\")\n            .push_hint(format!(\"If this is a table in the current database, move its declaration into module {database_module}\")));\n    };\n\n    // put wildcards last\n    columns.sort_by_key(|a| matches!(a, TyTupleField::Wildcard(_)));\n\n    let relation = rq::Relation {\n        kind: rq::RelationKind::ExternRef(LocalTable(extern_name)),\n        columns: tuple_fields_to_relation_columns(columns),\n    };\n    Ok((relation, None))\n}\n\nfn tuple_fields_to_relation_columns(columns: Vec<TyTupleField>) -> Vec<RelationColumn> {\n    columns\n        .into_iter()\n        .map(|field| match field {\n            TyTupleField::Single(name, _) => RelationColumn::Single(name),\n            TyTupleField::Wildcard(_) => RelationColumn::Wildcard,\n        })\n        .collect_vec()\n}\n\nfn validate_query_def(query_def: &QueryDef) -> Result<()> {\n    if let Some(requirement) = &query_def.version {\n        let current_version = compiler_version();\n\n        // We need to remove the pre-release part of the version, because\n        // otherwise those will fail the match.\n        let clean_version = Version {\n            pre: Prerelease::EMPTY,\n            ..current_version.clone()\n        };\n\n        if !requirement.matches(&clean_version) {\n            return Err(Error::new_simple(format!(\n                \"This query requires version {requirement} of PRQL that is not supported by prqlc version {clean_version} (shortened from {current_version}). Please upgrade the compiler.\"\n            )));\n        }\n    }\n    Ok(())\n}\n\n#[derive(Debug)]\nstruct Lowerer {\n    cid: IdGenerator<CId>,\n    tid: IdGenerator<TId>,\n\n    root_mod: RootModule,\n    database_module_path: Vec<String>,\n\n    /// describes what has certain id has been lowered to\n    node_mapping: HashMap<usize, LoweredTarget>,\n\n    /// mapping from [Ident] of [crate::pr::TableDef] into [TId]s\n    table_mapping: HashMap<Ident, TId>,\n\n    // current window for any new column defs\n    window: Option<rq::Window>,\n\n    /// A buffer to be added into current pipeline\n    pipeline: Vec<Transform>,\n\n    /// A buffer to be added into query tables\n    table_buffer: Vec<TableDecl>,\n}\n\n#[derive(Clone, EnumAsInner, Debug)]\nenum LoweredTarget {\n    /// Lowered node was a computed expression.\n    Compute(CId),\n\n    /// Lowered node was a pipeline input.\n    /// Contains mapping from column names to CIds, along with order in frame.\n    Input(HashMap<RelationColumn, (CId, usize)>),\n}\n\nimpl Lowerer {\n    fn new(root_mod: RootModule, database_module_path: &[String]) -> Self {\n        Lowerer {\n            root_mod,\n            database_module_path: database_module_path.to_vec(),\n\n            cid: IdGenerator::new(),\n            tid: IdGenerator::new(),\n\n            node_mapping: HashMap::new(),\n            table_mapping: HashMap::new(),\n\n            window: None,\n            pipeline: Vec::new(),\n            table_buffer: Vec::new(),\n        }\n    }\n\n    fn lower_table_decl(&mut self, table: decl::TableDecl, fq_ident: Ident) -> Result<()> {\n        let decl::TableDecl { ty, expr } = table;\n\n        // TODO: can this panic?\n        let columns = ty.unwrap().into_relation().unwrap();\n\n        let (relation, name) = match expr {\n            TableExpr::RelationVar(expr) => {\n                // a CTE\n                (self.lower_relation(*expr)?, Some(fq_ident.name.clone()))\n            }\n            TableExpr::LocalTable => {\n                extern_ref_to_relation(columns, &fq_ident, &self.database_module_path)?\n            }\n            TableExpr::Param(_) => unreachable!(),\n            TableExpr::None => return Ok(()),\n        };\n\n        let id = *self\n            .table_mapping\n            .entry(fq_ident)\n            .or_insert_with(|| self.tid.gen());\n\n        log::debug!(\"lowering table {name:?}, columns = {:?}\", relation.columns);\n\n        let table = TableDecl { id, name, relation };\n        self.table_buffer.push(table);\n        Ok(())\n    }\n\n    /// Lower an expression into a instance of a table in the query\n    fn lower_table_ref(&mut self, expr: pl::Expr) -> Result<rq::TableRef> {\n        let mut expr = expr;\n        if expr.lineage.is_none() {\n            // make sure that type of this expr has been inferred to be a table\n            expr.lineage = Some(Lineage::default());\n        }\n\n        Ok(match expr.kind {\n            pl::ExprKind::Ident(fq_table_name) => {\n                // ident that refer to table: create an instance of the table\n                let id = expr.id.unwrap();\n                let tid = *self\n                    .table_mapping\n                    .get(&fq_table_name)\n                    .ok_or_else(|| Error::new_bug(4474))?;\n\n                log::debug!(\"lowering an instance of table {fq_table_name} (id={id})...\");\n\n                let input_name = expr\n                    .lineage\n                    .as_ref()\n                    .and_then(|f| f.inputs.first())\n                    .map(|i| i.name.clone());\n                let name = input_name.or(Some(fq_table_name.name));\n\n                self.create_a_table_instance(id, name, tid)\n            }\n            pl::ExprKind::TransformCall(_) => {\n                // pipeline that has to be pulled out into a table\n                let id = expr.id.unwrap();\n\n                // create a new table\n                let tid = self.tid.gen();\n\n                let relation = self.lower_relation(expr)?;\n\n                let last_transform = &relation.kind.as_pipeline().unwrap().last().unwrap();\n                let cids = last_transform.as_select().unwrap().clone();\n\n                log::debug!(\"lowering inline table, columns = {:?}\", relation.columns);\n                self.table_buffer.push(TableDecl {\n                    id: tid,\n                    name: None,\n                    relation,\n                });\n\n                // return an instance of this new table\n                let table_ref = self.create_a_table_instance(id, None, tid);\n\n                let redirects = zip(cids, table_ref.columns.iter().map(|(_, c)| *c)).collect();\n                self.redirect_mappings(redirects);\n\n                table_ref\n            }\n            pl::ExprKind::SString(items) => {\n                let id = expr.id.unwrap();\n\n                // create a new table\n                let tid = self.tid.gen();\n\n                // pull columns from the table decl\n                let frame = expr.lineage.as_ref().unwrap();\n                let input = frame.inputs.first().unwrap();\n\n                let table_decl = self.root_mod.module.get(&input.table).unwrap();\n                let table_decl = table_decl.kind.as_table_decl().unwrap();\n                let ty = table_decl.ty.as_ref();\n                // TODO: can this panic?\n                let columns = ty.unwrap().as_relation().unwrap().clone();\n\n                log::debug!(\"lowering sstring table, columns = {columns:?}\");\n\n                // lower the expr\n                let items = self.lower_interpolations(items)?;\n                let columns = tuple_fields_to_relation_columns(columns);\n                let columns = try_extract_sql_columns(columns, &items);\n\n                let relation = rq::Relation {\n                    kind: rq::RelationKind::SString(items),\n                    columns,\n                };\n\n                self.table_buffer.push(TableDecl {\n                    id: tid,\n                    name: None,\n                    relation,\n                });\n\n                // return an instance of this new table\n                self.create_a_table_instance(id, None, tid)\n            }\n            pl::ExprKind::RqOperator { name, args } => {\n                let id = expr.id.unwrap();\n\n                // create a new table\n                let tid = self.tid.gen();\n\n                // pull columns from the table decl\n                let frame = expr.lineage.as_ref().unwrap();\n                let input = frame.inputs.first().unwrap();\n\n                let table_decl = self.root_mod.module.get(&input.table).unwrap();\n                let table_decl = table_decl.kind.as_table_decl().unwrap();\n                let ty = table_decl.ty.as_ref();\n                // TODO: can this panic?\n                let columns = ty.unwrap().as_relation().unwrap().clone();\n\n                log::debug!(\"lowering function table, columns = {columns:?}\");\n\n                // lower the expr\n                let args = args.into_iter().map(|a| self.lower_expr(a)).try_collect()?;\n                let relation = rq::Relation {\n                    kind: rq::RelationKind::BuiltInFunction { name, args },\n                    columns: tuple_fields_to_relation_columns(columns),\n                };\n\n                self.table_buffer.push(TableDecl {\n                    id: tid,\n                    name: None,\n                    relation,\n                });\n\n                // return an instance of this new table\n                self.create_a_table_instance(id, None, tid)\n            }\n\n            pl::ExprKind::Array(elements) => {\n                let id = expr.id.unwrap();\n\n                // create a new table\n                let tid = self.tid.gen();\n\n                // pull columns from the table decl\n                let lineage = expr.lineage.as_ref().unwrap();\n                let columns: Vec<_> = (lineage.columns.iter())\n                    .map(|c| match c {\n                        LineageColumn::Single { name, .. } => Ok(RelationColumn::Single(\n                            name.as_ref().map(|i| i.name.clone()),\n                        )),\n                        LineageColumn::All { .. } => Err(Error::new_bug(4317)),\n                    })\n                    .try_collect()?;\n\n                let lit = RelationLiteral {\n                    columns: columns\n                        .iter()\n                        .map(|c| c.as_single().unwrap().clone().unwrap())\n                        .collect_vec(),\n                    rows: elements\n                        .into_iter()\n                        .map(|row| {\n                            row.kind\n                                .into_tuple()\n                                .unwrap()\n                                .into_iter()\n                                .map(|element| {\n                                    element.try_cast(\n                                        |x| x.into_literal(),\n                                        Some(\"relation literal\"),\n                                        \"literals\",\n                                    )\n                                })\n                                .try_collect()\n                        })\n                        .try_collect()?,\n                };\n\n                log::debug!(\"lowering literal relation table, columns = {columns:?}\");\n                let relation = rq::Relation {\n                    kind: rq::RelationKind::Literal(lit),\n                    columns,\n                };\n\n                self.table_buffer.push(TableDecl {\n                    id: tid,\n                    name: None,\n                    relation,\n                });\n\n                // return an instance of this new table\n                self.create_a_table_instance(id, None, tid)\n            }\n\n            _ => {\n                let found_str = write_pl(expr.clone());\n                let mut error = Error::new(Reason::Expected {\n                    who: None,\n                    expected: \"a pipeline that resolves to a table\".to_string(),\n                    found: format!(\"`{}`\", found_str),\n                });\n\n                // Provide better hints for common mistakes\n                if found_str.starts_with(\"internal std.sub\") {\n                    // This is likely a negative number or expression that should be wrapped in parentheses\n                    error = error.push_hint(\n                        \"wrap negative numbers in parentheses, e.g. `sort (-column_name)`\",\n                    );\n                } else {\n                    error = error.push_hint(\"`from` statement might be missing?\");\n                }\n\n                return Err(error.with_span(expr.span));\n            }\n        })\n    }\n\n    fn redirect_mappings(&mut self, redirects: HashMap<CId, CId>) {\n        for target in self.node_mapping.values_mut() {\n            match target {\n                LoweredTarget::Compute(cid) => {\n                    if let Some(new) = redirects.get(cid) {\n                        *cid = *new;\n                    }\n                }\n                LoweredTarget::Input(mapping) => {\n                    for (cid, _) in mapping.values_mut() {\n                        if let Some(new) = redirects.get(cid) {\n                            *cid = *new;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fn create_a_table_instance(\n        &mut self,\n        id: usize,\n        name: Option<String>,\n        tid: TId,\n    ) -> rq::TableRef {\n        // create instance columns from table columns\n        let table = self.table_buffer.iter().find(|t| t.id == tid).unwrap();\n\n        let columns = (table.relation.columns.iter())\n            .cloned()\n            .unique()\n            .map(|col| (col, self.cid.gen()))\n            .collect_vec();\n\n        log::debug!(\"... columns = {columns:?}\");\n\n        let input_cids: HashMap<_, _> = columns\n            .iter()\n            .cloned()\n            .enumerate()\n            .map(|(index, (col, cid))| (col, (cid, index)))\n            .collect();\n        self.node_mapping\n            .insert(id, LoweredTarget::Input(input_cids));\n        rq::TableRef {\n            source: tid,\n            name,\n            columns,\n            prefer_cte: true,\n        }\n    }\n\n    fn lower_relation(&mut self, expr: pl::Expr) -> Result<rq::Relation> {\n        let span = expr.span;\n        let lineage = expr.lineage.clone();\n        let prev_pipeline = self.pipeline.drain(..).collect_vec();\n\n        self.lower_pipeline(expr, None)?;\n\n        let mut transforms = self.pipeline.drain(..).collect_vec();\n        let columns = self.push_select(lineage, &mut transforms).with_span(span)?;\n\n        self.pipeline = prev_pipeline;\n\n        let relation = rq::Relation {\n            kind: rq::RelationKind::Pipeline(transforms),\n            columns,\n        };\n        Ok(relation)\n    }\n\n    // Result is stored in self.pipeline\n    fn lower_pipeline(&mut self, ast: pl::Expr, closure_param: Option<usize>) -> Result<()> {\n        let transform_call = match ast.kind {\n            pl::ExprKind::TransformCall(transform) => transform,\n            pl::ExprKind::Func(closure) => {\n                let param = closure.params.first();\n                let param = param.and_then(|p| p.name.parse::<usize>().ok());\n                return self.lower_pipeline(*closure.body, param);\n            }\n            _ => {\n                if let Some(target) = ast.target_id {\n                    if Some(target) == closure_param {\n                        // ast is a closure param, so we can skip pushing From\n                        return Ok(());\n                    }\n                }\n\n                let table_ref = self.lower_table_ref(ast)?;\n                self.pipeline.push(Transform::From(table_ref));\n                return Ok(());\n            }\n        };\n\n        // lower input table\n        self.lower_pipeline(*transform_call.input, closure_param)?;\n\n        // ... and continues with transforms created in this function\n\n        let window = rq::Window {\n            frame: WindowFrame {\n                kind: transform_call.frame.kind,\n                range: self.lower_range(transform_call.frame.range)?,\n            },\n            partition: if let Some(partition) = transform_call.partition {\n                self.declare_as_columns(*partition, false)?\n            } else {\n                vec![]\n            },\n            sort: self.lower_sorts(transform_call.sort)?,\n        };\n        self.window = Some(window);\n\n        match *transform_call.kind {\n            pl::TransformKind::Derive { assigns, .. } => {\n                self.declare_as_columns(*assigns, false)?;\n            }\n            pl::TransformKind::Select { assigns, .. } => {\n                let cids = self.declare_as_columns(*assigns, false)?;\n                self.pipeline.push(Transform::Select(cids));\n            }\n            pl::TransformKind::Filter { filter, .. } => {\n                let filter = self.lower_expr(*filter)?;\n\n                self.pipeline.push(Transform::Filter(filter));\n            }\n            pl::TransformKind::Aggregate { assigns, .. } => {\n                let window = self.window.take();\n\n                let compute = self.declare_as_columns(*assigns, true)?;\n\n                let partition = window.unwrap().partition;\n                self.pipeline\n                    .push(Transform::Aggregate { partition, compute });\n            }\n            pl::TransformKind::Sort { by, .. } => {\n                let sorts = self.lower_sorts(by)?;\n                self.pipeline.push(Transform::Sort(sorts));\n            }\n            pl::TransformKind::Take { range, .. } => {\n                let window = self.window.take().unwrap_or_default();\n                let range = self.lower_range(range)?;\n\n                validate_take_range(&range, ast.span)?;\n\n                self.pipeline.push(Transform::Take(rq::Take {\n                    range,\n                    partition: window.partition,\n                    sort: window.sort,\n                }));\n            }\n            pl::TransformKind::Join {\n                side, with, filter, ..\n            } => {\n                let with = self.lower_table_ref(*with)?;\n\n                let transform = Transform::Join {\n                    side,\n                    with,\n                    filter: self.lower_expr(*filter)?,\n                };\n                self.pipeline.push(transform);\n            }\n            pl::TransformKind::Append(bottom) => {\n                let mut bottom = self.lower_table_ref(*bottom)?;\n                bottom.prefer_cte = false;\n\n                self.pipeline.push(Transform::Append(bottom));\n            }\n            pl::TransformKind::Loop(pipeline) => {\n                let relation = self.lower_relation(*pipeline)?;\n                let mut pipeline = relation.kind.into_pipeline().unwrap();\n\n                // last select is not needed here\n                pipeline.pop();\n\n                self.pipeline.push(Transform::Loop(pipeline));\n            }\n            pl::TransformKind::Group { .. } | pl::TransformKind::Window { .. } => unreachable!(\n                \"transform `{}` cannot be lowered.\",\n                (*transform_call.kind).as_ref()\n            ),\n        }\n        self.window = None;\n\n        // result is stored in self.pipeline\n        Ok(())\n    }\n\n    fn lower_range(&mut self, range: Range<Box<pl::Expr>>) -> Result<Range<rq::Expr>> {\n        Ok(Range {\n            start: range.start.map(|x| self.lower_expr(*x)).transpose()?,\n            end: range.end.map(|x| self.lower_expr(*x)).transpose()?,\n        })\n    }\n\n    fn lower_sorts(&mut self, by: Vec<ColumnSort<Box<pl::Expr>>>) -> Result<Vec<ColumnSort<CId>>> {\n        by.into_iter()\n            .map(|ColumnSort { column, direction }| {\n                let column = self.declare_as_column(*column, false)?;\n                Ok(ColumnSort { direction, column })\n            })\n            .try_collect()\n    }\n\n    /// Append a Select of final table columns derived from frame\n    fn push_select(\n        &mut self,\n        lineage: Option<Lineage>,\n        transforms: &mut Vec<Transform>,\n    ) -> Result<Vec<RelationColumn>> {\n        let lineage = lineage.unwrap_or_default();\n\n        log::debug!(\"push_select of a frame: {lineage:?}\");\n\n        let mut columns = Vec::new();\n\n        // normal columns\n        for col in &lineage.columns {\n            match col {\n                LineageColumn::Single {\n                    name,\n                    target_id,\n                    target_name,\n                } => {\n                    let cid = self.lookup_cid(*target_id, target_name.as_ref())?;\n\n                    let name = name.as_ref().map(|i| i.name.clone());\n                    columns.push((RelationColumn::Single(name), cid));\n                }\n                LineageColumn::All { input_id, except } => {\n                    let Some(input) = lineage.find_input(*input_id) else {\n                        return Err(Error::new_simple(\n                            \"column references a table not accessible in this context\",\n                        )\n                        .push_hint(\"join is not supported inside group\"));\n                    };\n\n                    match &self.node_mapping[&input.id] {\n                        LoweredTarget::Compute(_cid) => unreachable!(),\n                        LoweredTarget::Input(input_cols) => {\n                            let mut input_cols = input_cols\n                                .iter()\n                                .filter(|(c, _)| match c {\n                                    RelationColumn::Single(Some(name)) => !except.contains(name),\n                                    _ => true,\n                                })\n                                .collect_vec();\n                            input_cols.sort_by_key(|e| e.1 .1);\n\n                            for (col, (cid, _)) in input_cols {\n                                columns.push((col.clone(), *cid));\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        let (cols, cids) = columns.into_iter().unzip();\n\n        log::debug!(\"... cids={cids:?}\");\n        transforms.push(Transform::Select(cids));\n\n        Ok(cols)\n    }\n\n    fn declare_as_columns(&mut self, exprs: pl::Expr, is_aggregation: bool) -> Result<Vec<CId>> {\n        // special case: reference to a tuple that is a relational input\n        if exprs.ty.as_ref().is_some_and(|x| x.kind.is_tuple()) && exprs.kind.is_ident() {\n            // return all contained columns\n            let input_id = exprs.target_id.as_ref().unwrap();\n            let id_mapping = self.node_mapping.get(input_id).unwrap();\n            let input_columns = id_mapping.as_input().unwrap();\n            return Ok(input_columns\n                .iter()\n                .sorted_by_key(|c| c.1 .1)\n                .map(|(_, (cid, _))| *cid)\n                .collect_vec());\n        }\n\n        let mut r = Vec::new();\n\n        match exprs.kind {\n            pl::ExprKind::All { within, except } => {\n                // special case: ExprKind::All\n                r.extend(self.find_selected_all(*within, Some(*except))?);\n            }\n            pl::ExprKind::Tuple(fields) => {\n                // tuple unpacking\n                for expr in fields {\n                    r.extend(self.declare_as_columns(expr, is_aggregation)?);\n                }\n            }\n            _ => {\n                // base case\n                r.push(self.declare_as_column(exprs, is_aggregation)?);\n            }\n        }\n        Ok(r)\n    }\n\n    fn find_selected_all(\n        &mut self,\n        within: pl::Expr,\n        except: Option<pl::Expr>,\n    ) -> Result<Vec<CId>> {\n        let mut selected = self.declare_as_columns(within, false)?;\n        if let Some(except) = except {\n            let except: HashSet<_> = self.find_except_ids(except)?;\n            selected.retain(|t| !except.contains(t));\n        }\n        Ok(selected)\n    }\n\n    fn find_except_ids(&mut self, except: pl::Expr) -> Result<HashSet<CId>> {\n        let pl::ExprKind::Tuple(fields) = except.kind else {\n            return Ok(HashSet::new());\n        };\n\n        let mut res = HashSet::new();\n        for e in fields {\n            if e.target_id.is_none() {\n                continue;\n            }\n\n            let id = e.target_id.unwrap();\n            match e.kind {\n                pl::ExprKind::Ident(_) if e.ty.as_ref().is_some_and(|x| x.kind.is_tuple()) => {\n                    res.extend(self.find_selected_all(e, None).with_span(except.span)?);\n                }\n                pl::ExprKind::Ident(ident) => {\n                    res.insert(\n                        self.lookup_cid(id, Some(&ident.name))\n                            .with_span(except.span)?,\n                    );\n                }\n                pl::ExprKind::All { within, except } => {\n                    res.extend(self.find_selected_all(*within, Some(*except))?)\n                }\n                _ => {\n                    return Err(Error::new(Reason::Expected {\n                        who: None,\n                        expected: \"an identifier\".to_string(),\n                        found: write_pl(e),\n                    }));\n                }\n            }\n        }\n        Ok(res)\n    }\n\n    fn declare_as_column(\n        &mut self,\n        mut expr_ast: pl::Expr,\n        is_aggregation: bool,\n    ) -> Result<rq::CId> {\n        // short-circuit if this node has already been lowered\n        if let Some(LoweredTarget::Compute(lowered)) = self.node_mapping.get(&expr_ast.id.unwrap())\n        {\n            return Ok(*lowered);\n        }\n\n        // copy metadata before lowering\n        let alias = expr_ast.alias.clone();\n        let has_alias = alias.is_some();\n        let needs_window = expr_ast.needs_window;\n        expr_ast.needs_window = false;\n        let alias_for = if has_alias {\n            expr_ast.kind.as_ident().map(|x| x.name.clone())\n        } else {\n            None\n        };\n        let id = expr_ast.id.unwrap();\n\n        // lower\n        let expr = self.lower_expr(expr_ast)?;\n\n        // don't create new ColumnDef if expr is just a ColumnRef with no renaming\n        if let rq::ExprKind::ColumnRef(cid) = &expr.kind {\n            if !needs_window && (!has_alias || alias == alias_for) {\n                self.node_mapping.insert(id, LoweredTarget::Compute(*cid));\n                return Ok(*cid);\n            }\n        }\n\n        // determine window\n        let window = if needs_window {\n            self.window.clone()\n        } else {\n            None\n        };\n\n        // construct ColumnDef\n        let cid = self.cid.gen();\n        let compute = rq::Compute {\n            id: cid,\n            expr,\n            window,\n            is_aggregation,\n        };\n        self.node_mapping.insert(id, LoweredTarget::Compute(cid));\n\n        self.pipeline.push(Transform::Compute(compute));\n        Ok(cid)\n    }\n\n    fn lower_expr(&mut self, expr: pl::Expr) -> Result<rq::Expr> {\n        let span = expr.span;\n\n        if expr.needs_window {\n            let span = expr.span;\n            let cid = self.declare_as_column(expr, false)?;\n\n            let kind = rq::ExprKind::ColumnRef(cid);\n            return Ok(rq::Expr { kind, span });\n        }\n\n        let kind = match expr.kind {\n            pl::ExprKind::Ident(ident) => {\n                log::debug!(\"lowering ident {ident} (target {:?})\", expr.target_id);\n\n                if expr.ty.as_ref().is_some_and(|x| x.kind.is_tuple()) {\n                    // special case: tuple ref\n                    let expr = pl::Expr {\n                        kind: pl::ExprKind::Ident(ident),\n                        ..expr\n                    };\n                    let selected = self.find_selected_all(expr, None)?;\n\n                    if selected.len() == 1 {\n                        rq::ExprKind::ColumnRef(selected[0])\n                    } else {\n                        return Err(\n                            Error::new_simple(\"This wildcard usage is not yet supported.\")\n                                .with_span(span),\n                        );\n                    }\n                } else if let Some(id) = expr.target_id {\n                    // base case: column ref\n                    let cid = self.lookup_cid(id, Some(&ident.name)).with_span(span)?;\n\n                    rq::ExprKind::ColumnRef(cid)\n                } else {\n                    // fallback: unresolved ident\n                    // Let's hope that the database engine can resolve it.\n                    rq::ExprKind::SString(vec![InterpolateItem::String(ident.name)])\n                }\n            }\n            pl::ExprKind::All { within, except } => {\n                let selected = self.find_selected_all(*within, Some(*except))?;\n\n                if selected.len() == 1 {\n                    rq::ExprKind::ColumnRef(selected[0])\n                } else {\n                    return Err(\n                        Error::new_simple(\"This wildcard usage is not yet supported.\")\n                            .with_span(span),\n                    );\n                }\n            }\n            pl::ExprKind::Literal(literal) => rq::ExprKind::Literal(literal),\n\n            pl::ExprKind::SString(items) => {\n                rq::ExprKind::SString(self.lower_interpolations(items)?)\n            }\n            pl::ExprKind::FString(items) => {\n                let mut res = None;\n                for item in items {\n                    let item = Some(match item {\n                        pl::InterpolateItem::String(string) => str_lit(string),\n                        pl::InterpolateItem::Expr { expr, .. } => self.lower_expr(*expr)?,\n                    });\n\n                    res = rq::maybe_binop(res, \"std.concat\", item);\n                }\n\n                res.unwrap_or_else(|| str_lit(\"\".to_string())).kind\n            }\n            pl::ExprKind::Case(cases) => rq::ExprKind::Case(\n                cases\n                    .into_iter()\n                    .map(|case| -> Result<_> {\n                        Ok(SwitchCase {\n                            condition: self.lower_expr(*case.condition)?,\n                            value: self.lower_expr(*case.value)?,\n                        })\n                    })\n                    .try_collect()?,\n            ),\n            pl::ExprKind::RqOperator { name, args } => {\n                // Check for relation types used as operator arguments\n                for arg in &args {\n                    if arg.ty.as_ref().is_some_and(|x| x.is_relation()) {\n                        return Err(Error::new_simple(\n                            \"table variable cannot be used as a scalar value\",\n                        )\n                        .push_hint(\"use a join instead, or inline the subquery\")\n                        .with_span(arg.span));\n                    }\n                }\n\n                let args = args.into_iter().map(|x| self.lower_expr(x)).try_collect()?;\n\n                rq::ExprKind::Operator { name, args }\n            }\n            pl::ExprKind::Param(id) => rq::ExprKind::Param(id),\n\n            pl::ExprKind::Tuple(_) => {\n                return Err(\n                    Error::new_simple(\"table instance cannot be referenced directly\")\n                        .push_hint(\"column name might be missing?\")\n                        .with_span(span),\n                );\n            }\n\n            pl::ExprKind::Array(exprs) => rq::ExprKind::Array(\n                exprs\n                    .into_iter()\n                    .map(|x| self.lower_expr(x))\n                    .try_collect()?,\n            ),\n\n            pl::ExprKind::FuncCall(_) | pl::ExprKind::Func(_) | pl::ExprKind::TransformCall(_) => {\n                log::debug!(\"cannot lower {expr:?}\");\n                return Err(Error::new(Reason::Unexpected {\n                    found: format!(\"`{}`\", write_pl(expr.clone())),\n                })\n                .push_hint(\"this is probably a 'bad type' error (we are working on that)\")\n                .with_span(expr.span));\n            }\n\n            pl::ExprKind::Internal(_) => {\n                return Err(Error::new_assert(format!(\n                    \"Unresolved lowering: {}\",\n                    write_pl(expr)\n                )))\n            }\n        };\n\n        Ok(rq::Expr { kind, span })\n    }\n\n    fn lower_interpolations(\n        &mut self,\n        items: Vec<InterpolateItem<pl::Expr>>,\n    ) -> Result<Vec<InterpolateItem<rq::Expr>>> {\n        items\n            .into_iter()\n            .map(|i| {\n                Ok(match i {\n                    InterpolateItem::String(s) => InterpolateItem::String(s),\n                    InterpolateItem::Expr { expr, .. } => InterpolateItem::Expr {\n                        expr: Box::new(self.lower_expr(*expr)?),\n                        format: None,\n                    },\n                })\n            })\n            .try_collect()\n    }\n\n    fn lookup_cid(&mut self, id: usize, name: Option<&String>) -> Result<CId> {\n        let cid = match self.node_mapping.get(&id) {\n            Some(LoweredTarget::Compute(cid)) => *cid,\n            Some(LoweredTarget::Input(input_columns)) => {\n                let name = match name {\n                    Some(v) => RelationColumn::Single(Some(v.clone())),\n                    None => return Err(Error::new_simple(\n                        \"This table contains unnamed columns that need to be referenced by name\",\n                    )\n                    .with_span(self.root_mod.span_map.get(&id).cloned())\n                    .push_hint(\"the name may have been overridden later in the pipeline.\")),\n                };\n                log::trace!(\"lookup cid of name={name:?} in input {input_columns:?}\");\n\n                if let Some((cid, _)) = input_columns.get(&name) {\n                    *cid\n                } else {\n                    panic!(\"cannot find cid by id={id} and name={name:?}\");\n                }\n            }\n            None => {\n                return Err(Error::new_bug(3870))?;\n            }\n        };\n\n        Ok(cid)\n    }\n}\n\n/// Attempts to extract column names from an S-String to avoid wildcards when possible\nfn try_extract_sql_columns(\n    columns: Vec<RelationColumn>,\n    items: &[InterpolateItem<rq::Expr>],\n) -> Vec<RelationColumn> {\n    use sqlparser::ast;\n\n    let mut has_wildcard = false;\n\n    let sql_columns = items\n        .iter()\n        .map(|item| match item {\n            InterpolateItem::String(s) => {\n                let sql_ast =\n                    sqlparser::parser::Parser::parse_sql(&sqlparser::dialect::GenericDialect {}, s)\n                        .map_err(|err| format!(\"could not parse {item:?}: {err:?}\"))?;\n                if sql_ast.len() != 1 {\n                    return Err(format!(\n                        \"expected exactly one statement, got {}\",\n                        sql_ast.len()\n                    ));\n                }\n\n                let statement = sql_ast.into_iter().next().unwrap();\n\n                if let sqlparser::ast::Statement::Query(query) = statement {\n                    if let sqlparser::ast::SetExpr::Select(select_stmt) = *query.body {\n                        select_stmt\n                            .projection\n                            .into_iter()\n                            .map(|expr| match expr {\n                                ast::SelectItem::UnnamedExpr(expr) => {\n                                    if let ast::Expr::Identifier(ast::Ident { value, .. }) = expr {\n                                        Ok(value)\n                                    } else {\n                                        Err(format!(\"Only Idents are supported, got {expr:?}\"))\n                                    }\n                                }\n                                ast::SelectItem::ExprWithAlias { alias, .. } => Ok(alias.value), // Store alias\n                                ast::SelectItem::QualifiedWildcard(_, _)\n                                | ast::SelectItem::Wildcard(_) => {\n                                    has_wildcard = true;\n                                    Err(\"columns contain a wildcard\".into())\n                                }\n                            })\n                            .collect::<Result<Vec<String>, String>>()\n                    } else {\n                        Err(format!(\"not a SELECT statement: {query:?}\"))\n                    }\n                } else {\n                    Err(format!(\"not a Query: {statement:?}\"))\n                }\n            }\n            InterpolateItem::Expr { .. } => Err(format!(\n                \"could not extract columns from item {item:?}: not a string\"\n            )),\n        })\n        .collect::<Result<Vec<Vec<String>>, _>>();\n\n    let sql_columns = match sql_columns {\n        Ok(sql_columns) => sql_columns,\n        Err(cause) => {\n            log::warn!(\"Could not extract SQL columns: {cause}\");\n            return columns;\n        }\n    }\n    .into_iter()\n    .flatten()\n    // deduplicate extracted columns, but preserve their order\n    .collect::<BTreeSet<String>>();\n\n    if has_wildcard {\n        log::debug!(\"s-string contains a wildcard, skipping column extraction\");\n        return columns;\n    }\n\n    columns\n        .into_iter()\n        .filter(|column| matches!(column, RelationColumn::Single(_)))\n        .chain(\n            sql_columns\n                .into_iter()\n                .map(|col| RelationColumn::Single(Some(col))),\n        )\n        .collect()\n}\n\nfn str_lit(string: String) -> rq::Expr {\n    rq::Expr {\n        kind: rq::ExprKind::Literal(Literal::String(string)),\n        span: None,\n    }\n}\n\nfn validate_take_range(range: &Range<rq::Expr>, span: Option<Span>) -> Result<()> {\n    fn bound_as_int(bound: &Option<rq::Expr>) -> Option<Option<&i64>> {\n        bound\n            .as_ref()\n            .map(|e| e.kind.as_literal().and_then(|l| l.as_integer()))\n    }\n\n    fn bound_display(bound: Option<Option<&i64>>) -> String {\n        bound\n            .map(|x| x.map(|l| l.to_string()).unwrap_or_else(|| \"?\".to_string()))\n            .unwrap_or_default()\n    }\n\n    let start = bound_as_int(&range.start);\n    let end = bound_as_int(&range.end);\n\n    let start_ok = if let Some(start) = start {\n        start.map(|s| *s >= 1).unwrap_or(false)\n    } else {\n        true\n    };\n\n    let end_ok = if let Some(end) = end {\n        end.map(|e| *e >= 1).unwrap_or(false)\n    } else {\n        true\n    };\n\n    if !start_ok || !end_ok {\n        let range_display = format!(\"{}..{}\", bound_display(start), bound_display(end));\n        Err(Error::new(Reason::Expected {\n            who: Some(\"take\".to_string()),\n            expected: \"a positive int range\".to_string(),\n            found: range_display,\n        })\n        .with_span(span))\n    } else {\n        Ok(())\n    }\n}\n\n#[derive(Default)]\nstruct TableExtractor {\n    path: Vec<String>,\n\n    tables: Vec<(Ident, (decl::TableDecl, Option<usize>))>,\n}\n\nimpl TableExtractor {\n    /// Finds table declarations in a module, recursively.\n    fn extract(root_module: &Module) -> Vec<(Ident, (decl::TableDecl, Option<usize>))> {\n        let mut te = TableExtractor::default();\n        te.extract_from_module(root_module);\n        te.tables\n    }\n\n    /// Finds table declarations in a module, recursively.\n    fn extract_from_module(&mut self, namespace: &Module) {\n        for (name, entry) in &namespace.names {\n            self.path.push(name.clone());\n\n            match &entry.kind {\n                DeclKind::Module(ns) => {\n                    self.extract_from_module(ns);\n                }\n                DeclKind::TableDecl(table) => {\n                    let fq_ident = Ident::from_path(self.path.clone());\n                    self.tables\n                        .push((fq_ident, (table.clone(), entry.declared_at)));\n                }\n                _ => {}\n            }\n            self.path.pop();\n        }\n    }\n}\n\n/// Does a topological sort of the pipeline definitions and prunes all definitions that\n/// are not needed for the main pipeline. To do this, it needs to collect references\n/// between pipelines.\nfn toposort_tables(\n    tables: Vec<(Ident, (decl::TableDecl, Option<usize>))>,\n    main_table: &Ident,\n) -> Vec<(Ident, (decl::TableDecl, Option<usize>))> {\n    let tables: HashMap<_, _, RandomState> = HashMap::from_iter(tables);\n\n    let mut dependencies: Vec<(Ident, Vec<Ident>)> = Vec::new();\n    for (ident, table) in &tables {\n        let deps = if let TableExpr::RelationVar(e) = &table.0.expr {\n            TableDepsCollector::collect(*e.clone())\n        } else {\n            vec![]\n        };\n\n        dependencies.push((ident.clone(), deps));\n    }\n\n    // sort just to make sure lowering is stable\n    dependencies.sort_by(|a, b| a.0.cmp(&b.0));\n\n    let sort = toposort(&dependencies, Some(main_table)).unwrap();\n\n    let mut tables = tables;\n    sort.into_iter()\n        .map(|ident| tables.remove_entry(ident).unwrap())\n        .collect_vec()\n}\n\n#[derive(Default)]\nstruct TableDepsCollector {\n    deps: Vec<Ident>,\n}\n\nimpl TableDepsCollector {\n    fn collect(expr: pl::Expr) -> Vec<Ident> {\n        let mut c = TableDepsCollector::default();\n        c.fold_expr(expr).unwrap();\n        c.deps\n    }\n}\n\nimpl PlFold for TableDepsCollector {\n    fn fold_expr(&mut self, mut expr: pl::Expr) -> Result<pl::Expr> {\n        expr.kind = match expr.kind {\n            pl::ExprKind::Ident(ref ident) => {\n                if let Some(ty) = &expr.ty {\n                    if ty.is_relation() {\n                        self.deps.push(ident.clone());\n                    }\n                }\n                expr.kind\n            }\n            pl::ExprKind::TransformCall(tc) => {\n                pl::ExprKind::TransformCall(self.fold_transform_call(tc)?)\n            }\n            pl::ExprKind::Func(func) => pl::ExprKind::Func(Box::new(self.fold_func(*func)?)),\n\n            // optimization: don't recurse into anything else than TransformCalls and Func\n            _ => expr.kind,\n        };\n        Ok(expr)\n    }\n}\n\nfn get_span_of_id(l: &Lowerer, id: Option<usize>) -> Option<Span> {\n    id.and_then(|id| l.root_mod.span_map.get(&id)).cloned()\n}\n\nfn with_span_if_not_exists<'a, F>(get_span: F) -> impl FnOnce(Error) -> Error + 'a\nwhere\n    F: FnOnce() -> Option<Span> + 'a,\n{\n    move |e| {\n        if e.span.is_some() {\n            return e;\n        }\n\n        e.with_span(get_span())\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/mod.rs",
    "content": "//! Semantic resolver (name resolution, type checking and lowering to RQ)\n\npub mod ast_expand;\nmod lowering;\nmod module;\npub mod reporting;\nmod resolver;\n\npub use lowering::lower_to_ir;\n\nuse self::resolver::Resolver;\npub use self::resolver::ResolverOptions;\nuse crate::ir::decl::{Module, RootModule};\nuse crate::ir::pl::{self, ImportDef, ModuleDef, Stmt, StmtKind, TypeDef, VarDef};\nuse crate::ir::rq::RelationalQuery;\nuse crate::parser::is_mod_def_for;\nuse crate::pr;\nuse crate::WithErrorInfo;\nuse crate::{debug, parser};\nuse crate::{Error, Reason, Result};\n\n/// Runs semantic analysis on the query and lowers PL to RQ.\npub fn resolve_and_lower(\n    file_tree: pr::ModuleDef,\n    main_path: &[String],\n    database_module_path: Option<&[String]>,\n) -> Result<RelationalQuery> {\n    let root_mod = resolve(file_tree)?;\n\n    debug::log_stage(debug::Stage::Semantic(debug::StageSemantic::Lowering));\n    let default_db = [NS_DEFAULT_DB.to_string()];\n    let database_module_path = database_module_path.unwrap_or(&default_db);\n    let (query, _) = lowering::lower_to_ir(root_mod, main_path, database_module_path)?;\n\n    debug::log_entry(|| debug::DebugEntryKind::ReprRq(query.clone()));\n    Ok(query)\n}\n\n/// Runs semantic analysis on the query.\npub fn resolve(mut module_tree: pr::ModuleDef) -> Result<RootModule> {\n    load_std_lib(&mut module_tree);\n\n    // expand AST into PL\n    debug::log_stage(debug::Stage::Semantic(debug::StageSemantic::AstExpand));\n    let root_module_def = ast_expand::expand_module_def(module_tree)?;\n    debug::log_entry(|| debug::DebugEntryKind::ReprPl(root_module_def.clone()));\n\n    // init new root module\n    let mut root_module = RootModule {\n        module: Module::new_root(),\n        ..Default::default()\n    };\n    let mut resolver = Resolver::new(&mut root_module);\n\n    // resolve the module def into the root module\n    debug::log_stage(debug::Stage::Semantic(debug::StageSemantic::Resolver));\n    resolver.fold_statements(root_module_def.stmts)?;\n    debug::log_entry(|| debug::DebugEntryKind::ReprDecl(root_module.clone()));\n\n    Ok(root_module)\n}\n\n/// Preferred way of injecting std module.\npub fn load_std_lib(module_tree: &mut pr::ModuleDef) {\n    if !module_tree.stmts.iter().any(|s| is_mod_def_for(s, NS_STD)) {\n        log::debug!(\"loading std.prql\");\n        let _suppressed = debug::log_suppress();\n\n        let std_source = include_str!(\"std.prql\");\n        match parser::parse_source(std_source, 0) {\n            Ok(stmts) => {\n                let stmt = pr::Stmt::new(pr::StmtKind::ModuleDef(pr::ModuleDef {\n                    name: \"std\".to_string(),\n                    stmts,\n                }));\n                module_tree.stmts.insert(0, stmt);\n            }\n            Err(errs) => {\n                panic!(\"std.prql failed to compile:\\n{errs:?}\");\n            }\n        }\n    }\n}\n\npub fn is_ident_or_func_call(expr: &pl::Expr, name: &pr::Ident) -> bool {\n    match &expr.kind {\n        pl::ExprKind::Ident(i) if i == name => true,\n        pl::ExprKind::FuncCall(pl::FuncCall { name: n_expr, .. })\n            if n_expr.kind.as_ident() == Some(name) =>\n        {\n            true\n        }\n        _ => false,\n    }\n}\n\npub const NS_STD: &str = \"std\";\npub const NS_THIS: &str = \"this\";\npub const NS_THAT: &str = \"that\";\npub const NS_PARAM: &str = \"_param\";\npub const NS_DEFAULT_DB: &str = \"default_db\";\npub const NS_QUERY_DEF: &str = \"prql\";\npub const NS_MAIN: &str = \"main\";\n\n// refers to the containing module (direct parent)\npub const NS_SELF: &str = \"_self\";\n\n// implies we can infer new non-module declarations in the containing module\npub const NS_INFER: &str = \"_infer\";\n\n// implies we can infer new module declarations in the containing module\npub const NS_INFER_MODULE: &str = \"_infer_module\";\n\nimpl Stmt {\n    pub fn new(kind: StmtKind) -> Stmt {\n        Stmt {\n            id: None,\n            kind,\n            span: None,\n            annotations: Vec::new(),\n        }\n    }\n\n    pub(crate) fn name(&self) -> &str {\n        match &self.kind {\n            StmtKind::QueryDef(_) => NS_QUERY_DEF,\n            StmtKind::VarDef(VarDef { name, .. }) => name,\n            StmtKind::TypeDef(TypeDef { name, .. }) => name,\n            StmtKind::ModuleDef(ModuleDef { name, .. }) => name,\n            StmtKind::ImportDef(ImportDef { name, alias }) => alias.as_ref().unwrap_or(&name.name),\n        }\n    }\n}\n\nimpl pl::Expr {\n    fn try_cast<T, F, S2: ToString>(self, f: F, who: Option<&str>, expected: S2) -> Result<T, Error>\n    where\n        F: FnOnce(pl::ExprKind) -> Result<T, pl::ExprKind>,\n    {\n        f(self.kind).map_err(|i| {\n            Error::new(Reason::Expected {\n                who: who.map(|s| s.to_string()),\n                expected: expected.to_string(),\n                found: format!(\"`{}`\", write_pl(pl::Expr::new(i))),\n            })\n            .with_span(self.span)\n        })\n    }\n}\n\n/// Write a PL IR to string.\n///\n/// Because PL needs to be restricted back to AST, ownerships of expr is required.\npub fn write_pl(expr: pl::Expr) -> String {\n    let expr = ast_expand::restrict_expr(expr);\n\n    crate::codegen::write_expr(&expr)\n}\n#[cfg(test)]\npub mod test {\n    use insta::assert_yaml_snapshot;\n\n    use super::{resolve, resolve_and_lower, RootModule};\n    use crate::ir::rq::RelationalQuery;\n    use crate::parser::parse;\n    use crate::Errors;\n\n    pub fn parse_resolve_and_lower(query: &str) -> Result<RelationalQuery, Errors> {\n        let source_tree = query.into();\n        Ok(resolve_and_lower(parse(&source_tree)?, &[], None)?)\n    }\n\n    pub fn parse_and_resolve(query: &str) -> Result<RootModule, Errors> {\n        let source_tree = query.into();\n        Ok(resolve(parse(&source_tree)?)?)\n    }\n\n    #[test]\n    fn test_resolve_01() {\n        assert_yaml_snapshot!(parse_resolve_and_lower(r###\"\n        from employees\n        select !{foo}\n        \"###).unwrap().relation.columns, @\"- Wildcard\")\n    }\n\n    #[test]\n    fn test_resolve_02() {\n        assert_yaml_snapshot!(parse_resolve_and_lower(r###\"\n        from foo\n        sort day\n        window range:-4..4 (\n            derive {next_four_days = sum b}\n        )\n        \"###).unwrap().relation.columns, @r\"\n        - Single: day\n        - Single: b\n        - Wildcard\n        - Single: next_four_days\n        \")\n    }\n\n    #[test]\n    fn test_resolve_03() {\n        assert_yaml_snapshot!(parse_resolve_and_lower(r###\"\n        from a=albums\n        filter is_sponsored\n        select {a.*}\n        \"###).unwrap().relation.columns, @r\"\n        - Single: is_sponsored\n        - Wildcard\n        \")\n    }\n\n    #[test]\n    fn test_resolve_04() {\n        assert_yaml_snapshot!(parse_resolve_and_lower(r###\"\n        from x\n        select {a, a, a = a + 1}\n        \"###).unwrap().relation.columns, @r\"\n        - Single: ~\n        - Single: ~\n        - Single: a\n        \")\n    }\n\n    #[test]\n    fn test_header() {\n        assert_yaml_snapshot!(parse_resolve_and_lower(r#\"\n        prql target:sql.mssql version:\"0\"\n\n        from employees\n        \"#).unwrap(), @r\"\n        def:\n          version: ^0\n          other:\n            target: sql.mssql\n        tables:\n          - id: 0\n            name: ~\n            relation:\n              kind:\n                ExternRef:\n                  LocalTable:\n                    - employees\n              columns:\n                - Wildcard\n        relation:\n          kind:\n            Pipeline:\n              - From:\n                  source: 0\n                  columns:\n                    - - Wildcard\n                      - 0\n                  name: employees\n                  prefer_cte: true\n              - Select:\n                  - 0\n          columns:\n            - Wildcard\n        \" );\n\n        assert!(parse_resolve_and_lower(\n            r###\"\n        prql target:sql.bigquery version:foo\n        from employees\n        \"###,\n        )\n        .is_err());\n\n        assert!(parse_resolve_and_lower(\n            r#\"\n        prql target:sql.bigquery version:\"25\"\n        from employees\n        \"#,\n        )\n        .is_err());\n\n        assert!(parse_resolve_and_lower(\n            r###\"\n        prql target:sql.yah version:foo\n        from employees\n        \"###,\n        )\n        .is_err());\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/module.rs",
    "content": "use std::collections::{HashMap, HashSet};\n\nuse super::{\n    NS_DEFAULT_DB, NS_INFER, NS_INFER_MODULE, NS_MAIN, NS_PARAM, NS_QUERY_DEF, NS_SELF, NS_STD,\n    NS_THAT, NS_THIS,\n};\nuse crate::ir::decl::{Decl, DeclKind, Module, RootModule, TableDecl, TableExpr};\nuse crate::ir::pl::{Annotation, Expr, Ident, Lineage, LineageColumn};\nuse crate::pr::QueryDef;\nuse crate::pr::{Span, Ty, TyKind, TyTupleField};\nuse crate::Error;\nuse crate::Result;\n\nimpl Module {\n    pub fn singleton<S: ToString>(name: S, entry: Decl) -> Module {\n        Module {\n            names: HashMap::from([(name.to_string(), entry)]),\n            ..Default::default()\n        }\n    }\n\n    pub fn new_root() -> Module {\n        // Each module starts with a default namespace that contains a wildcard\n        // and the standard library.\n        Module {\n            names: HashMap::from([\n                (\n                    NS_DEFAULT_DB.to_string(),\n                    Decl::from(DeclKind::Module(Module::new_database())),\n                ),\n                (NS_STD.to_string(), Decl::from(DeclKind::default())),\n            ]),\n            shadowed: None,\n            redirects: vec![\n                Ident::from_name(NS_THIS),\n                Ident::from_name(NS_THAT),\n                Ident::from_name(NS_PARAM),\n                Ident::from_name(NS_STD),\n            ],\n        }\n    }\n\n    pub fn new_database() -> Module {\n        let names = HashMap::from([\n            (\n                NS_INFER.to_string(),\n                Decl::from(DeclKind::Infer(Box::new(DeclKind::TableDecl(TableDecl {\n                    ty: Some(Ty::relation(vec![TyTupleField::Wildcard(None)])),\n                    expr: TableExpr::LocalTable,\n                })))),\n            ),\n            (\n                NS_INFER_MODULE.to_string(),\n                Decl::from(DeclKind::Infer(Box::new(DeclKind::Module(Module {\n                    names: HashMap::new(),\n                    redirects: vec![],\n                    shadowed: None,\n                })))),\n            ),\n        ]);\n        Module {\n            names,\n            shadowed: None,\n            redirects: vec![],\n        }\n    }\n\n    pub fn insert(&mut self, fq_ident: Ident, decl: Decl) -> Result<Option<Decl>, Error> {\n        if fq_ident.path.is_empty() {\n            Ok(self.names.insert(fq_ident.name, decl))\n        } else {\n            let (top_level, remaining) = fq_ident.pop_front();\n            let entry = self.names.entry(top_level).or_default();\n\n            if let DeclKind::Module(inner) = &mut entry.kind {\n                inner.insert(remaining.unwrap(), decl)\n            } else {\n                Err(Error::new_simple(\n                    \"path does not resolve to a module or a table\",\n                ))\n            }\n        }\n    }\n\n    pub fn get_mut(&mut self, ident: &Ident) -> Option<&mut Decl> {\n        let mut ns = self;\n\n        for part in &ident.path {\n            let entry = ns.names.get_mut(part);\n\n            match entry {\n                Some(Decl {\n                    kind: DeclKind::Module(inner),\n                    ..\n                }) => {\n                    ns = inner;\n                }\n                _ => return None,\n            }\n        }\n\n        ns.names.get_mut(&ident.name)\n    }\n\n    /// Get namespace entry using a fully qualified ident.\n    pub fn get(&self, fq_ident: &Ident) -> Option<&Decl> {\n        let mut ns = self;\n\n        for (index, part) in fq_ident.path.iter().enumerate() {\n            let decl = ns.names.get(part)?;\n\n            match &decl.kind {\n                DeclKind::Module(inner) => {\n                    ns = inner;\n                }\n                DeclKind::LayeredModules(stack) => {\n                    let next = fq_ident.path.get(index + 1).unwrap_or(&fq_ident.name);\n                    let mut found = false;\n                    for n in stack.iter().rev() {\n                        if n.names.contains_key(next) {\n                            ns = n;\n                            found = true;\n                            break;\n                        }\n                    }\n                    if !found {\n                        return None;\n                    }\n                }\n                _ => return None,\n            }\n        }\n\n        ns.names.get(&fq_ident.name)\n    }\n\n    pub fn lookup(&self, ident: &Ident) -> HashSet<Ident> {\n        fn lookup_in(module: &Module, ident: Ident) -> HashSet<Ident> {\n            let (prefix, ident) = ident.pop_front();\n\n            if let Some(ident) = ident {\n                if let Some(entry) = module.names.get(&prefix) {\n                    let redirected = match &entry.kind {\n                        DeclKind::Module(ns) => ns.lookup(&ident),\n                        DeclKind::LayeredModules(stack) => {\n                            let mut r = HashSet::new();\n                            for ns in stack.iter().rev() {\n                                r = ns.lookup(&ident);\n\n                                if !r.is_empty() {\n                                    break;\n                                }\n                            }\n                            r\n                        }\n                        _ => HashSet::new(),\n                    };\n\n                    return redirected\n                        .into_iter()\n                        .map(|i| Ident::from_name(&prefix) + i)\n                        .collect();\n                }\n            } else if let Some(decl) = module.names.get(&prefix) {\n                if let DeclKind::Module(inner) = &decl.kind {\n                    if inner.names.contains_key(NS_SELF) {\n                        return HashSet::from([Ident::from_path(vec![\n                            prefix,\n                            NS_SELF.to_string(),\n                        ])]);\n                    }\n                }\n\n                return HashSet::from([Ident::from_name(prefix)]);\n            }\n            HashSet::new()\n        }\n\n        log::trace!(\"lookup: {ident}\");\n\n        let mut res = HashSet::new();\n\n        res.extend(lookup_in(self, ident.clone()));\n\n        for redirect in &self.redirects {\n            log::trace!(\"... following redirect {redirect}\");\n            let r = lookup_in(self, redirect.clone() + ident.clone());\n            log::trace!(\"... result of redirect {redirect}: {r:?}\");\n            res.extend(r);\n        }\n        res\n    }\n\n    pub(super) fn insert_frame(&mut self, lineage: &Lineage, namespace: &str) {\n        let namespace = self.names.entry(namespace.to_string()).or_default();\n        let namespace = namespace.kind.as_module_mut().unwrap();\n\n        let lin_ty = *ty_of_lineage(lineage).kind.into_array().unwrap().unwrap();\n\n        for (col_index, column) in lineage.columns.iter().enumerate() {\n            // determine input name\n            let input_name = match column {\n                LineageColumn::All { input_id, .. } => {\n                    lineage.find_input(*input_id).map(|i| &i.name)\n                }\n                LineageColumn::Single { name, .. } => name.as_ref().and_then(|n| n.path.first()),\n            };\n\n            // get or create input namespace\n            let ns;\n            if let Some(input_name) = input_name {\n                let entry = match namespace.names.get_mut(input_name) {\n                    Some(x) => x,\n                    None => {\n                        namespace.redirects.push(Ident::from_name(input_name));\n\n                        let input = lineage.find_input_by_name(input_name).unwrap();\n                        let order = lineage.inputs.iter().position(|i| i.id == input.id);\n                        let order = order.unwrap();\n\n                        let mut sub_ns = Module::default();\n\n                        let self_ty = lin_ty.clone().kind.into_tuple().unwrap();\n                        let self_ty = self_ty\n                            .into_iter()\n                            .flat_map(|x| x.into_single())\n                            .find(|(name, _)| name.as_ref() == Some(input_name))\n                            .and_then(|(_, ty)| ty)\n                            .or(Some(Ty::new(TyKind::Tuple(vec![TyTupleField::Wildcard(\n                                None,\n                            )]))));\n\n                        let self_decl = Decl {\n                            declared_at: Some(input.id),\n                            kind: DeclKind::InstanceOf(input.table.clone(), self_ty),\n                            ..Default::default()\n                        };\n                        sub_ns.names.insert(NS_SELF.to_string(), self_decl);\n\n                        let sub_ns = Decl {\n                            declared_at: Some(input.id),\n                            order,\n                            kind: DeclKind::Module(sub_ns),\n                            ..Default::default()\n                        };\n\n                        namespace.names.entry(input_name.clone()).or_insert(sub_ns)\n                    }\n                };\n                ns = entry.kind.as_module_mut().unwrap()\n            } else {\n                ns = namespace;\n            }\n\n            // insert column decl\n            match column {\n                LineageColumn::All { input_id, .. } => {\n                    // Input might not exist if lineage references an outer scope\n                    // (e.g., join inside group). This is an error caught during\n                    // lowering - skip here to avoid panic during resolution.\n                    if let Some(input) = lineage.find_input(*input_id) {\n                        let kind = DeclKind::Infer(Box::new(DeclKind::Column(input.id)));\n                        let declared_at = Some(input.id);\n                        let decl = Decl {\n                            kind,\n                            declared_at,\n                            order: col_index + 1,\n                            ..Default::default()\n                        };\n                        ns.names.insert(NS_INFER.to_string(), decl);\n                    }\n                }\n                LineageColumn::Single {\n                    name: Some(name),\n                    target_id,\n                    ..\n                } => {\n                    let decl = Decl {\n                        kind: DeclKind::Column(*target_id),\n                        declared_at: None,\n                        order: col_index + 1,\n                        ..Default::default()\n                    };\n                    ns.names.insert(name.name.clone(), decl);\n                }\n                _ => {}\n            }\n        }\n\n        // insert namespace._self with correct type\n        namespace.names.insert(\n            NS_SELF.to_string(),\n            Decl::from(DeclKind::InstanceOf(Ident::from_name(\"\"), Some(lin_ty))),\n        );\n    }\n\n    pub(super) fn insert_frame_col(&mut self, namespace: &str, name: String, id: usize) {\n        let namespace = self.names.entry(namespace.to_string()).or_default();\n        let namespace = namespace.kind.as_module_mut().unwrap();\n\n        namespace.names.insert(name, DeclKind::Column(id).into());\n    }\n\n    pub fn shadow(&mut self, ident: &str) {\n        let shadowed = self.names.remove(ident).map(Box::new);\n        let entry = DeclKind::Module(Module {\n            shadowed,\n            ..Default::default()\n        });\n        self.names.insert(ident.to_string(), entry.into());\n    }\n\n    pub fn unshadow(&mut self, ident: &str) {\n        if let Some(entry) = self.names.remove(ident) {\n            let ns = entry.kind.into_module().unwrap();\n\n            if let Some(shadowed) = ns.shadowed {\n                self.names.insert(ident.to_string(), *shadowed);\n            }\n        }\n    }\n\n    pub fn stack_push(&mut self, ident: &str, namespace: Module) {\n        let entry = self\n            .names\n            .entry(ident.to_string())\n            .or_insert_with(|| DeclKind::LayeredModules(Vec::new()).into());\n        let stack = entry.kind.as_layered_modules_mut().unwrap();\n\n        stack.push(namespace);\n    }\n\n    pub fn stack_pop(&mut self, ident: &str) -> Option<Module> {\n        (self.names.get_mut(ident))\n            .and_then(|e| e.kind.as_layered_modules_mut())\n            .and_then(|stack| stack.pop())\n    }\n\n    pub(crate) fn into_exprs(self) -> HashMap<String, Expr> {\n        self.names\n            .into_iter()\n            .map(|(k, v)| (k, *v.kind.into_expr().unwrap()))\n            .collect()\n    }\n\n    pub(crate) fn from_exprs(exprs: HashMap<String, Expr>) -> Module {\n        Module {\n            names: exprs\n                .into_iter()\n                .map(|(key, expr)| {\n                    let decl = Decl {\n                        kind: DeclKind::Expr(Box::new(expr)),\n                        ..Default::default()\n                    };\n                    (key, decl)\n                })\n                .collect(),\n            ..Default::default()\n        }\n    }\n\n    pub fn as_decls(&self) -> Vec<(Ident, &Decl)> {\n        let mut r = Vec::new();\n        for (name, decl) in &self.names {\n            match &decl.kind {\n                DeclKind::Module(module) => r.extend(\n                    module\n                        .as_decls()\n                        .into_iter()\n                        .map(|(inner, decl)| (Ident::from_name(name) + inner, decl)),\n                ),\n                _ => r.push((Ident::from_name(name), decl)),\n            }\n        }\n        r\n    }\n}\n\ntype HintAndSpan = (Option<String>, Option<Span>);\n\nimpl RootModule {\n    pub(super) fn declare(\n        &mut self,\n        ident: Ident,\n        decl: DeclKind,\n        id: Option<usize>,\n        annotations: Vec<Annotation>,\n    ) -> Result<()> {\n        let existing = self.module.get(&ident);\n        if existing.is_some() {\n            return Err(Error::new_simple(format!(\n                \"duplicate declarations of {ident}\"\n            )));\n        }\n\n        let decl = Decl {\n            kind: decl,\n            declared_at: id,\n            order: 0,\n            annotations,\n        };\n        self.module.insert(ident, decl).unwrap();\n        Ok(())\n    }\n\n    /// Finds that main pipeline given a path to either main itself or its parent module.\n    /// Returns main expr and fq ident of the decl.\n    pub fn find_main_rel(&self, path: &[String]) -> Result<(&TableExpr, Ident), HintAndSpan> {\n        let (decl, ident) = self.find_main(path).map_err(|x| (x, None))?;\n\n        let span = decl\n            .declared_at\n            .and_then(|id| self.span_map.get(&id))\n            .cloned();\n\n        let decl = (decl.kind.as_table_decl())\n            .ok_or((Some(format!(\"{ident} is not a relational variable\")), span))?;\n\n        Ok((&decl.expr, ident))\n    }\n\n    pub fn find_main(&self, path: &[String]) -> Result<(&Decl, Ident), Option<String>> {\n        let mut tried_idents = Vec::new();\n\n        // is path referencing the relational var directly?\n        if !path.is_empty() {\n            let ident = Ident::from_path(path.to_vec());\n            let decl = self.module.get(&ident);\n\n            if let Some(decl) = decl {\n                return Ok((decl, ident));\n            } else {\n                tried_idents.push(ident.to_string());\n            }\n        }\n\n        // is path referencing the parent module?\n        {\n            let mut path = path.to_vec();\n            path.push(NS_MAIN.to_string());\n\n            let ident = Ident::from_path(path);\n            let decl = self.module.get(&ident);\n\n            if let Some(decl) = decl {\n                return Ok((decl, ident));\n            } else {\n                tried_idents.push(ident.to_string());\n            }\n        }\n\n        Err(Some(format!(\n            \"Expected a declaration at {}\",\n            tried_idents.join(\" or \")\n        )))\n    }\n\n    pub fn find_query_def(&self, main: &Ident) -> Option<&QueryDef> {\n        let ident = Ident {\n            path: main.path.clone(),\n            name: NS_QUERY_DEF.to_string(),\n        };\n\n        let decl = self.module.get(&ident)?;\n        decl.kind.as_query_def()\n    }\n}\n\npub fn ty_of_lineage(lineage: &Lineage) -> Ty {\n    Ty::relation(\n        lineage\n            .columns\n            .iter()\n            .map(|col| match col {\n                LineageColumn::All { .. } => TyTupleField::Wildcard(None),\n                LineageColumn::Single { name, .. } => {\n                    TyTupleField::Single(name.as_ref().map(|i| i.name.clone()), None)\n                }\n            })\n            .collect(),\n    )\n}\n\n#[cfg(test)]\nmod tests {\n    use prqlc_parser::lexer::lr::Literal;\n\n    use super::*;\n    use crate::ir::pl::ExprKind;\n\n    // TODO: tests / docstrings for `stack_pop` & `stack_push` & `insert_frame`\n    #[test]\n    fn test_module() {\n        let mut module = Module::default();\n\n        let ident = Ident::from_name(\"test_name\");\n        let expr: Expr = Expr::new(ExprKind::Literal(Literal::Integer(42)));\n        let decl: Decl = DeclKind::Expr(Box::new(expr)).into();\n\n        assert!(module.insert(ident.clone(), decl.clone()).is_ok());\n        assert_eq!(module.get(&ident).unwrap(), &decl);\n        assert_eq!(module.get_mut(&ident).unwrap(), &decl);\n\n        // Lookup\n        let lookup_result = module.lookup(&ident);\n        assert_eq!(lookup_result.len(), 1);\n        assert!(lookup_result.contains(&ident));\n    }\n\n    #[test]\n    fn test_module_shadow_unshadow() {\n        let mut module = Module::default();\n\n        let ident = Ident::from_name(\"test_name\");\n        let expr: Expr = Expr::new(ExprKind::Literal(Literal::Integer(42)));\n        let decl: Decl = DeclKind::Expr(Box::new(expr)).into();\n\n        module.insert(ident.clone(), decl.clone()).unwrap();\n\n        module.shadow(\"test_name\");\n        assert!(module.get(&ident) != Some(&decl));\n\n        module.unshadow(\"test_name\");\n        assert_eq!(module.get(&ident).unwrap(), &decl);\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/reporting.rs",
    "content": "use std::collections::HashMap;\nuse std::ops::Range;\n\nuse ariadne::{Color, Label, Report, ReportBuilder, ReportKind, Source};\nuse schemars::JsonSchema;\nuse serde::Serialize;\n\nuse crate::ir::decl::{DeclKind, Module, RootModule, TableDecl, TableExpr};\nuse crate::ir::pl;\nuse crate::ir::pl::PlFold;\nuse crate::pr;\nuse crate::{Result, Span};\n\npub fn label_references(root_mod: &RootModule, source_id: String, source: String) -> Vec<u8> {\n    let report_span = (source_id.clone(), 0..source.len());\n\n    let mut report = Report::build(ReportKind::Custom(\"Info\", Color::Blue), report_span);\n\n    let source = Source::from(source);\n\n    // label all idents and function calls\n    let mut labeler = Labeler {\n        root_mod,\n        source: &source,\n        source_id: &source_id,\n        report: &mut report,\n    };\n    labeler.label_module(&labeler.root_mod.module);\n\n    let mut out = Vec::new();\n    report\n        .finish()\n        .write((source_id, source), &mut out)\n        .unwrap();\n    out\n}\n\n/// Traverses AST and add labels for each of the idents and function calls\nstruct Labeler<'a> {\n    root_mod: &'a RootModule,\n    source: &'a Source,\n    source_id: &'a str,\n    report: &'a mut ReportBuilder<'static, (String, Range<usize>)>,\n}\n\nimpl Labeler<'_> {\n    fn label_module(&mut self, module: &Module) {\n        for (_, decl) in module.names.iter() {\n            if let DeclKind::TableDecl(TableDecl {\n                expr: TableExpr::RelationVar(expr),\n                ..\n            }) = &decl.kind\n            {\n                self.fold_expr(*expr.clone()).unwrap();\n            }\n        }\n    }\n\n    fn get_span_lines(&mut self, id: usize) -> Option<String> {\n        let decl_span = self.root_mod.span_map.get(&id);\n        decl_span.map(|decl_span| {\n            let line_span = self.source.get_line_range(&Range::from(*decl_span));\n            if line_span.len() <= 1 {\n                format!(\" at line {}\", line_span.start + 1)\n            } else {\n                format!(\" at lines {}-{}\", line_span.start + 1, line_span.end)\n            }\n        })\n    }\n}\n\nimpl pl::PlFold for Labeler<'_> {\n    fn fold_expr(&mut self, node: pl::Expr) -> Result<pl::Expr> {\n        if let Some(ident) = node.kind.as_ident() {\n            if let Some(span) = node.span {\n                let decl = self.root_mod.module.get(ident);\n\n                let ident = format!(\"[{ident}]\");\n\n                let (decl, color) = if let Some(decl) = decl {\n                    let color = match &decl.kind {\n                        DeclKind::Expr(_) => Color::Blue,\n                        DeclKind::Ty(_) => Color::Green,\n                        DeclKind::Column { .. } => Color::Yellow,\n                        DeclKind::InstanceOf(_, _) => Color::Yellow,\n                        DeclKind::TableDecl { .. } => Color::Red,\n                        DeclKind::Module(module) => {\n                            self.label_module(module);\n\n                            Color::Cyan\n                        }\n                        DeclKind::LayeredModules(_) => Color::Cyan,\n                        DeclKind::Infer(_) => Color::White,\n                        DeclKind::QueryDef(_) => Color::White,\n                        DeclKind::Import(_) => Color::White,\n                    };\n\n                    let location = decl\n                        .declared_at\n                        .and_then(|id| self.get_span_lines(id))\n                        .unwrap_or_default();\n\n                    let decl = match &decl.kind {\n                        DeclKind::TableDecl(TableDecl { ty, .. }) => {\n                            format!(\n                                \"table {}\",\n                                ty.as_ref().and_then(|t| t.name.clone()).unwrap_or_default()\n                            )\n                        }\n                        _ => decl.to_string(),\n                    };\n\n                    (format!(\"{decl}{location}\"), color)\n                } else if let Some(decl_id) = node.target_id {\n                    let lines = self.get_span_lines(decl_id).unwrap_or_default();\n\n                    (format!(\"variable{lines}\"), Color::Yellow)\n                } else {\n                    (\"\".to_string(), Color::White)\n                };\n\n                let label_span = (self.source_id.to_string(), span.start..span.end);\n\n                self.report.add_label(\n                    Label::new(label_span)\n                        .with_message(format!(\"{ident} {decl}\"))\n                        .with_color(color),\n                );\n            }\n        }\n        Ok(pl::Expr {\n            kind: self.fold_expr_kind(node.kind)?,\n            ..node\n        })\n    }\n}\n\n/// Traverses AST and collects all node.frame\npub fn collect_frames(expr: pl::Expr) -> FrameCollector {\n    let mut collector = FrameCollector {\n        frames: vec![],\n        nodes: vec![],\n        ast: None,\n    };\n\n    collector.fold_expr(expr).unwrap();\n\n    collector.frames.reverse();\n\n    let mut parent_updates = Vec::new();\n    let mut node_pos = HashMap::new();\n    for (i, node) in collector.nodes.iter().enumerate() {\n        node_pos.insert(node.id, i);\n        for &child in &node.children {\n            parent_updates.push((child, node.id));\n        }\n    }\n\n    for (child, parent) in parent_updates {\n        if let Some(child_pos) = node_pos.get(&child) {\n            if let Some(child_node) = collector.nodes.get_mut(*child_pos) {\n                child_node.parent = Some(parent);\n            }\n        }\n    }\n\n    collector\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]\npub struct ExprGraphNode {\n    /// Node unique ID\n    pub id: usize,\n\n    /// Descriptive text about the node\n    pub kind: String,\n\n    /// Position of this expr in the original source query\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub span: Option<Span>,\n\n    /// When this node is part of a Tuple, this holds the alias name\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub alias: Option<String>,\n\n    /// When kind is Ident, this holds the referenced name\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub ident: Option<pl::ExprKind>,\n\n    /// Upstream sources of data for this expr as node IDs\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub targets: Vec<usize>,\n\n    /// If this expr holds other exprs, these are their node IDs\n    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n    pub children: Vec<usize>,\n\n    /// If this expr is inside of another expr, this is its parent node ID\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub parent: Option<usize>,\n}\n\n#[derive(Serialize, JsonSchema)]\npub struct FrameCollector {\n    /// Each transformation step in the main pipeline corresponds to a single\n    /// frame. This holds the output columns at each frame, as well as the span\n    /// position of the frame.\n    pub frames: Vec<(Option<Span>, pl::Lineage)>,\n\n    /// A mapping of expression graph node IDs to their node definitions.\n    pub nodes: Vec<ExprGraphNode>,\n\n    /// The parsed AST from the provided query.\n    pub ast: Option<pr::ModuleDef>,\n}\n\nimpl PlFold for FrameCollector {\n    fn fold_expr(&mut self, expr: pl::Expr) -> Result<pl::Expr> {\n        if let Some(id) = expr.id {\n            let targets = match &expr.kind {\n                pl::ExprKind::Ident(_) => {\n                    if let Some(target_id) = expr.target_id {\n                        vec![target_id]\n                    } else {\n                        vec![]\n                    }\n                }\n                pl::ExprKind::RqOperator { args, .. } => args.iter().filter_map(|e| e.id).collect(),\n                pl::ExprKind::Case(switch) => switch\n                    .iter()\n                    .flat_map(|c| vec![c.condition.id.unwrap(), c.value.id.unwrap()])\n                    .collect(),\n                pl::ExprKind::SString(iv) | pl::ExprKind::FString(iv) => iv\n                    .iter()\n                    .filter_map(|i| match i {\n                        pl::InterpolateItem::Expr { expr: e, .. } => e.id,\n                        _ => None,\n                    })\n                    .collect(),\n                _ => vec![],\n            };\n\n            let ident = if matches!(&expr.kind, pl::ExprKind::Ident(_)) {\n                Some(expr.kind.clone())\n            } else {\n                None\n            };\n\n            let children = match &expr.kind {\n                pl::ExprKind::Tuple(args) | pl::ExprKind::Array(args) => {\n                    args.iter().filter_map(|e| e.id).collect()\n                }\n                pl::ExprKind::TransformCall(tc) => {\n                    let mut tcc = vec![tc.input.id.unwrap()];\n\n                    match *tc.kind {\n                        pl::TransformKind::Derive { assigns: ref e }\n                        | pl::TransformKind::Select { assigns: ref e }\n                        | pl::TransformKind::Filter { filter: ref e }\n                        | pl::TransformKind::Append(ref e)\n                        | pl::TransformKind::Loop(ref e)\n                        | pl::TransformKind::Group {\n                            pipeline: ref e, ..\n                        }\n                        | pl::TransformKind::Window {\n                            pipeline: ref e, ..\n                        } => {\n                            tcc.push(e.id.unwrap());\n                        }\n                        pl::TransformKind::Aggregate { assigns: ref e } => {\n                            tcc.push(e.id.unwrap());\n                            if let Some(p) = &tc.partition {\n                                tcc.push(p.id.unwrap())\n                            }\n                        }\n                        pl::TransformKind::Join {\n                            ref with,\n                            ref filter,\n                            ..\n                        } => {\n                            tcc.push(with.id.unwrap());\n                            tcc.push(filter.id.unwrap());\n                        }\n                        pl::TransformKind::Take { ref range } => {\n                            if let Some(e) = &range.start {\n                                tcc.push(e.id.unwrap());\n                            }\n                            if let Some(e) = &range.end {\n                                tcc.push(e.id.unwrap());\n                            }\n                        }\n                        pl::TransformKind::Sort { ref by } => {\n                            for c in by {\n                                tcc.push(c.column.id.unwrap());\n                            }\n                        }\n                    };\n\n                    tcc\n                }\n                _ => vec![],\n            };\n\n            let kind = match &expr.kind {\n                pl::ExprKind::TransformCall(tc) => {\n                    let tc_kind = tc.kind.as_ref().as_ref().to_string();\n\n                    format!(\"TransformCall: {tc_kind}\")\n                }\n                _ => expr.kind.as_ref().to_string(),\n            };\n\n            self.nodes.push(ExprGraphNode {\n                id,\n                kind,\n                span: expr.span,\n                alias: expr.alias.clone(),\n                ident,\n                targets,\n                children,\n                parent: None,\n            });\n        }\n\n        self.nodes.sort_by(|a, b| a.id.cmp(&b.id));\n        self.nodes.dedup();\n\n        if matches!(expr.kind, pl::ExprKind::TransformCall(_)) {\n            let lineage = expr.lineage.clone();\n            if let Some(lineage) = lineage {\n                self.frames.push((expr.span, lineage));\n            }\n        }\n\n        Ok(pl::Expr {\n            kind: self.fold_expr_kind(expr.kind)?,\n            ..expr\n        })\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/expr.rs",
    "content": "use itertools::Itertools;\n\nuse crate::ir::decl::{DeclKind, Module};\nuse crate::ir::pl;\nuse crate::ir::pl::PlFold;\nuse crate::pr::{Ty, TyKind, TyTupleField};\nuse crate::semantic::resolver::{flatten, types, Resolver};\nuse crate::semantic::{NS_INFER, NS_SELF, NS_THAT, NS_THIS};\nuse crate::utils::IdGenerator;\nuse crate::Result;\nuse crate::{Error, Reason, Span, WithErrorInfo};\n\nimpl pl::PlFold for Resolver<'_> {\n    fn fold_stmts(&mut self, _: Vec<pl::Stmt>) -> Result<Vec<pl::Stmt>> {\n        unreachable!()\n    }\n\n    fn fold_type(&mut self, ty: Ty) -> Result<Ty> {\n        Ok(match ty.kind {\n            TyKind::Ident(ident) => {\n                self.root_mod.module.shadow(NS_THIS);\n                self.root_mod.module.shadow(NS_THAT);\n\n                let fq_ident = self.resolve_ident(&ident)?;\n\n                let decl = self.root_mod.module.get(&fq_ident).unwrap();\n                let decl_ty = decl.kind.as_ty().ok_or_else(|| {\n                    Error::new(Reason::Expected {\n                        who: None,\n                        expected: \"a type\".to_string(),\n                        found: decl.to_string(),\n                    })\n                })?;\n                let mut ty = decl_ty.clone();\n                ty.name = ty.name.or(Some(fq_ident.name));\n\n                self.root_mod.module.unshadow(NS_THIS);\n                self.root_mod.module.unshadow(NS_THAT);\n\n                ty\n            }\n            _ => pl::fold_type(self, ty)?,\n        })\n    }\n\n    fn fold_var_def(&mut self, var_def: pl::VarDef) -> Result<pl::VarDef> {\n        let value = match var_def.value {\n            Some(value) if matches!(value.kind, pl::ExprKind::Func(_)) => Some(value),\n            Some(value) => Some(Box::new(flatten::Flattener::fold(self.fold_expr(*value)?))),\n            None => None,\n        };\n\n        Ok(pl::VarDef {\n            name: var_def.name,\n            value,\n            ty: var_def.ty.map(|x| self.fold_type(x)).transpose()?,\n        })\n    }\n\n    fn fold_expr(&mut self, node: pl::Expr) -> Result<pl::Expr> {\n        if node.id.is_some() && !matches!(node.kind, pl::ExprKind::Func(_)) {\n            return Ok(node);\n        }\n\n        let id = self.id.gen();\n        let alias = Box::new(node.alias.clone());\n        let span = Box::new(node.span);\n\n        if let Some(span) = *span {\n            self.root_mod.span_map.insert(id, span);\n        }\n\n        log::trace!(\"folding expr [{id:?}] {node:?}\");\n\n        let r = match node.kind {\n            pl::ExprKind::Ident(ident) => {\n                log::debug!(\"resolving ident {ident}...\");\n                let fq_ident = self\n                    .resolve_ident(&ident)\n                    .map_err(|e| e.with_span(node.span))?;\n                log::debug!(\"... resolved to {fq_ident}\");\n                let entry = self.root_mod.module.get(&fq_ident).unwrap();\n                log::debug!(\"... which is {entry}\");\n\n                match &entry.kind {\n                    DeclKind::Infer(_) => pl::Expr {\n                        kind: pl::ExprKind::Ident(fq_ident),\n                        target_id: entry.declared_at,\n                        ..node\n                    },\n                    DeclKind::Column(target_id) => pl::Expr {\n                        kind: pl::ExprKind::Ident(fq_ident),\n                        target_id: Some(*target_id),\n                        ..node\n                    },\n\n                    DeclKind::TableDecl(_) => {\n                        let input_name = ident.name.clone();\n\n                        let lineage = self.lineage_of_table_decl(&fq_ident, input_name, id);\n\n                        pl::Expr {\n                            kind: pl::ExprKind::Ident(fq_ident),\n                            ty: Some(ty_of_lineage(&lineage)),\n                            lineage: Some(lineage),\n                            alias: None,\n                            ..node\n                        }\n                    }\n\n                    DeclKind::Expr(expr) => match &expr.kind {\n                        pl::ExprKind::Func(closure) => {\n                            let closure = self.fold_function_types(closure.clone())?;\n\n                            let expr = pl::Expr::new(pl::ExprKind::Func(closure));\n\n                            if self.in_func_call_name {\n                                expr\n                            } else {\n                                self.fold_expr(expr)?\n                            }\n                        }\n                        _ => self.fold_expr(expr.as_ref().clone())?,\n                    },\n\n                    DeclKind::InstanceOf(_, ty) => {\n                        let ty = ty.clone();\n\n                        let fields = self.construct_wildcard_include(&fq_ident);\n\n                        pl::Expr {\n                            kind: pl::ExprKind::Tuple(fields),\n                            ty,\n                            ..node\n                        }\n                    }\n\n                    DeclKind::Ty(_) => {\n                        return Err(Error::new(Reason::Expected {\n                            who: None,\n                            expected: \"a value\".to_string(),\n                            found: \"a type\".to_string(),\n                        })\n                        .with_span(*span));\n                    }\n\n                    _ => pl::Expr {\n                        kind: pl::ExprKind::Ident(fq_ident),\n                        ..node\n                    },\n                }\n            }\n\n            pl::ExprKind::FuncCall(pl::FuncCall { name, args, .. })\n                if (name.kind.as_ident()).is_some_and(|i| i.to_string() == \"std.not\")\n                    && matches!(args[0].kind, pl::ExprKind::Tuple(_)) =>\n            {\n                let arg = args.into_iter().exactly_one().unwrap();\n                self.resolve_column_exclusion(arg)?\n            }\n\n            pl::ExprKind::FuncCall(pl::FuncCall {\n                name,\n                args,\n                named_args,\n            }) => {\n                // fold function name\n                self.default_namespace = None;\n                let old = self.in_func_call_name;\n                self.in_func_call_name = true;\n                let name = Box::new(self.fold_expr(*name)?);\n                self.in_func_call_name = old;\n\n                let func = name.try_cast(|n| n.into_func(), None, \"a function\")?;\n\n                // fold function\n                let func = self.apply_args_to_closure(func, args, named_args)?;\n                self.fold_function(func, *span)?\n            }\n\n            pl::ExprKind::Func(closure) => self.fold_function(closure, *span)?,\n\n            pl::ExprKind::Tuple(exprs) => {\n                let exprs = self.fold_exprs(exprs)?;\n\n                // flatten\n                let exprs = exprs\n                    .into_iter()\n                    .flat_map(|e| match e.kind {\n                        pl::ExprKind::Tuple(items) if e.flatten => items,\n                        _ => vec![e],\n                    })\n                    .collect_vec();\n\n                pl::Expr {\n                    kind: pl::ExprKind::Tuple(exprs),\n                    ..node\n                }\n            }\n\n            item => pl::Expr {\n                kind: pl::fold_expr_kind(self, item)?,\n                ..node\n            },\n        };\n        self.finish_expr_resolve(r, id, *alias, *span)\n    }\n}\n\nimpl Resolver<'_> {\n    fn finish_expr_resolve(\n        &mut self,\n        expr: pl::Expr,\n        id: usize,\n        alias: Option<String>,\n        span: Option<Span>,\n    ) -> Result<pl::Expr> {\n        let mut r = Box::new(self.maybe_static_eval(expr)?);\n\n        r.id = r.id.or(Some(id));\n        r.alias = r.alias.or(alias);\n        r.span = r.span.or(span);\n\n        if r.ty.is_none() {\n            r.ty = Resolver::infer_type(&r)?;\n        }\n        if r.lineage.is_none() {\n            if let pl::ExprKind::TransformCall(call) = &r.kind {\n                r.lineage = Some(call.infer_lineage()?);\n            } else if let Some(relation_columns) = r.ty.as_ref().and_then(|t| t.as_relation()) {\n                log::debug!(\"found a relational type without lineage: declaring a new table for it: {relation_columns:?}\");\n\n                // lineage from ty\n                let columns = Some(relation_columns.clone());\n\n                let name = r.alias.clone();\n                let frame = self.declare_table_for_literal(id, columns, name);\n\n                r.lineage = Some(frame);\n            }\n        }\n        if let Some(lineage) = &mut r.lineage {\n            if let Some(alias) = r.alias.take() {\n                lineage.rename(alias.clone());\n\n                if let Some(ty) = &mut r.ty {\n                    types::rename_relation(&mut ty.kind, alias);\n                }\n            }\n        }\n        Ok(*r)\n    }\n\n    pub fn resolve_column_exclusion(&mut self, expr: pl::Expr) -> Result<pl::Expr> {\n        let expr = self.fold_expr(expr)?;\n        let except = self.coerce_into_tuple(expr)?;\n\n        self.fold_expr(pl::Expr::new(pl::ExprKind::All {\n            within: Box::new(pl::Expr::new(pl::Ident::from_name(NS_THIS))),\n            except: Box::new(except),\n        }))\n    }\n\n    pub fn construct_wildcard_include(&mut self, module_fq_self: &pl::Ident) -> Vec<pl::Expr> {\n        let module_fq = module_fq_self.clone().pop().unwrap();\n\n        let decl = self.root_mod.module.get(&module_fq).unwrap();\n        let module = decl.kind.as_module().unwrap();\n\n        let prefix = module_fq.iter().collect_vec();\n        Self::construct_tuple_from_module(&mut self.id, &prefix, module)\n    }\n\n    pub fn construct_tuple_from_module(\n        id: &mut IdGenerator<usize>,\n        prefix: &[&String],\n        module: &Module,\n    ) -> Vec<pl::Expr> {\n        let mut res = Vec::new();\n\n        if let Some(decl) = module.names.get(NS_INFER) {\n            let wildcard_field = pl::Expr {\n                id: Some(id.gen()),\n                target_id: decl.declared_at,\n                flatten: true,\n                ty: Some(Ty::new(TyKind::Tuple(vec![TyTupleField::Wildcard(None)]))),\n                ..pl::Expr::new(pl::Ident::from_name(NS_SELF))\n            };\n            return vec![wildcard_field];\n        }\n\n        for (name, decl) in module.names.iter().sorted_by_key(|(_, d)| d.order) {\n            res.push(match &decl.kind {\n                DeclKind::Module(submodule) => {\n                    let prefix = [prefix.to_vec(), vec![name]].concat();\n                    let sub_fields = Self::construct_tuple_from_module(id, &prefix, submodule);\n                    pl::Expr {\n                        id: Some(id.gen()),\n                        alias: Some(name.clone()),\n                        ..pl::Expr::new(pl::ExprKind::Tuple(sub_fields))\n                    }\n                }\n                DeclKind::Column(target_id) => pl::Expr {\n                    id: Some(id.gen()),\n                    target_id: Some(*target_id),\n                    // alias: Some(name.clone()),\n                    ..pl::Expr::new(pl::Ident::from_path([prefix.to_vec(), vec![name]].concat()))\n                },\n                _ => continue,\n            });\n        }\n        res\n    }\n}\n\nfn ty_of_lineage(lineage: &pl::Lineage) -> Ty {\n    Ty::relation(\n        lineage\n            .columns\n            .iter()\n            .map(|col| match col {\n                pl::LineageColumn::All { .. } => TyTupleField::Wildcard(None),\n                pl::LineageColumn::Single { name, .. } => {\n                    TyTupleField::Single(name.as_ref().map(|i| i.name.clone()), None)\n                }\n            })\n            .collect(),\n    )\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/flatten.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::ir::pl::{\n    fold_column_sorts, fold_transform_kind, ColumnSort, Expr, ExprKind, PlFold, TransformCall,\n    TransformKind, WindowFrame,\n};\nuse crate::Result;\n\n/// Flattens group and window [TransformCall]s into a single pipeline.\n/// Sets partition, window and sort of [TransformCall].\n#[derive(Default, Debug)]\npub struct Flattener {\n    /// Sort affects downstream transforms in a pipeline.\n    /// Because transform pipelines are represented by nested [TransformCall]s,\n    /// affected transforms are all ancestor nodes of sort [TransformCall].\n    /// This means that this field has to be set after folding inner table,\n    /// so it's passed to parent call of `fold_transform_call`\n    sort: Vec<ColumnSort>,\n\n    sort_undone: bool,\n\n    /// Group affects transforms in it's inner pipeline.\n    /// This means that this field has to be set before folding inner pipeline,\n    /// and unset after the folding.\n    partition: Option<Box<Expr>>,\n\n    /// Window affects transforms in it's inner pipeline.\n    /// This means that this field has to be set before folding inner pipeline,\n    /// and unset after the folding.\n    window: WindowFrame,\n\n    /// Window and group contain Closures in their inner pipelines.\n    /// These closures have form similar to this function:\n    /// ```prql\n    /// let closure = tbl_chunk -> (derive ... (sort ... (tbl_chunk)))\n    /// ```\n    /// To flatten a window or group, we need to replace group/window transform\n    /// with their closure's body and replace `tbl_chunk` with pipeline\n    /// preceding the group/window transform.\n    ///\n    /// That's what `replace_map` is for.\n    replace_map: HashMap<usize, Expr>,\n}\n\nimpl Flattener {\n    pub fn fold(expr: Expr) -> Expr {\n        let mut f = Flattener::default();\n        f.fold_expr(expr).unwrap()\n    }\n}\n\nimpl PlFold for Flattener {\n    fn fold_expr(&mut self, mut expr: Expr) -> Result<Expr> {\n        if let Some(target) = &expr.target_id {\n            if let Some(replacement) = self.replace_map.remove(target) {\n                return Ok(replacement);\n            }\n        }\n\n        expr.kind = match expr.kind {\n            ExprKind::TransformCall(t) => {\n                log::debug!(\"flattening {}\", (*t.kind).as_ref());\n\n                let (input, kind) = match *t.kind {\n                    TransformKind::Sort { by } => {\n                        // fold\n                        let by = fold_column_sorts(self, by)?;\n                        let input = self.fold_expr(*t.input)?;\n\n                        self.sort.clone_from(&by);\n\n                        if self.sort_undone {\n                            return Ok(input);\n                        } else {\n                            (input, TransformKind::Sort { by })\n                        }\n                    }\n                    TransformKind::Group { by, pipeline } => {\n                        let sort_undone = self.sort_undone;\n                        // Only mark sort as undone if there's an actual partition.\n                        // Empty group {} should preserve sort (fixes #5100).\n                        if !matches!(by.kind, ExprKind::Tuple(ref fields) if fields.is_empty()) {\n                            self.sort_undone = true;\n                        }\n\n                        let input = self.fold_expr(*t.input)?;\n\n                        let pipeline = pipeline.kind.into_func().unwrap();\n\n                        let table_param = &pipeline.params[0];\n                        let param_id = table_param.name.parse::<usize>().unwrap();\n\n                        self.replace_map.insert(param_id, input);\n                        self.partition = Some(by);\n                        self.sort.clear();\n\n                        let pipeline = self.fold_expr(*pipeline.body)?;\n\n                        self.replace_map.remove(&param_id);\n                        self.partition = None;\n                        self.sort.clear();\n                        self.sort_undone = sort_undone;\n\n                        // If the pipeline simplified to a non-TransformCall (e.g., sort was\n                        // dropped), use the pipeline's lineage since the original GROUP lineage\n                        // may reference expressions that no longer exist in the tree.\n                        // Otherwise, preserve the GROUP's lineage which includes the `by` columns.\n                        let lineage = if matches!(pipeline.kind, ExprKind::TransformCall(_)) {\n                            expr.lineage\n                        } else {\n                            pipeline.lineage\n                        };\n\n                        return Ok(Expr {\n                            ty: expr.ty,\n                            lineage,\n                            ..pipeline\n                        });\n                    }\n                    TransformKind::Window {\n                        kind,\n                        range,\n                        pipeline,\n                    } => {\n                        let tbl = self.fold_expr(*t.input)?;\n                        let pipeline = pipeline.kind.into_func().unwrap();\n\n                        let table_param = &pipeline.params[0];\n                        let param_id = table_param.name.parse::<usize>().unwrap();\n\n                        self.replace_map.insert(param_id, tbl);\n                        self.window = WindowFrame { kind, range };\n\n                        let pipeline = self.fold_expr(*pipeline.body)?;\n\n                        self.window = WindowFrame::default();\n                        self.replace_map.remove(&param_id);\n\n                        return Ok(Expr {\n                            ty: expr.ty,\n                            lineage: expr.lineage,\n                            ..pipeline\n                        });\n                    }\n                    kind => (self.fold_expr(*t.input)?, fold_transform_kind(self, kind)?),\n                };\n\n                // In case we're appending or joining another pipeline, we do not want to apply the\n                // sub-pipeline's sort, as it may result in column lookup errors. Without this, we\n                // would try to join on `album_id` in the outer pipeline of the following query, but\n                // the column does not exist\n                //\n                // from artists\n                // join side:left (\n                //   from albums\n                //   sort {`album_id`}\n                //   derive {`album_name` = `name`}\n                //   select {`artist_id`, `album_name`}\n                // ) (this.id == that.artist_id)\n                let sort = if matches!(kind, TransformKind::Join { .. } | TransformKind::Append(_))\n                {\n                    vec![]\n                } else {\n                    self.sort.clone()\n                };\n\n                ExprKind::TransformCall(TransformCall {\n                    input: Box::new(input),\n                    kind: Box::new(kind),\n                    partition: self.partition.clone(),\n                    frame: self.window.clone(),\n                    sort,\n                })\n            }\n            kind => self.fold_expr_kind(kind)?,\n        };\n        Ok(expr)\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/functions.rs",
    "content": "use std::collections::HashMap;\nuse std::iter::zip;\n\nuse itertools::{Itertools, Position};\n\nuse super::Resolver;\nuse crate::ir::decl::{Decl, DeclKind, Module};\nuse crate::ir::pl::*;\nuse crate::pr::{Ty, TyFunc};\nuse crate::semantic::resolver::types;\nuse crate::semantic::{NS_PARAM, NS_THAT, NS_THIS};\nuse crate::Result;\nuse crate::{Error, Reason, Span, WithErrorInfo};\n\nimpl Resolver<'_> {\n    pub fn fold_function(&mut self, closure: Box<Func>, span: Option<Span>) -> Result<Expr> {\n        let closure = self.fold_function_types(closure)?;\n\n        log::debug!(\n            \"func {} {}/{} params\",\n            closure.as_debug_name(),\n            closure.args.len(),\n            closure.params.len()\n        );\n\n        if closure.args.len() > closure.params.len() {\n            return Err(Error::new_simple(format!(\n                \"Too many arguments to function `{}`\",\n                closure.as_debug_name()\n            ))\n            .with_span(span));\n        }\n\n        let enough_args = closure.args.len() == closure.params.len();\n        if !enough_args {\n            return Ok(*expr_of_func(closure, span));\n        }\n\n        // make sure named args are pushed into params\n        let closure = if !closure.named_params.is_empty() {\n            self.apply_args_to_closure(closure, [].into(), [].into())?\n        } else {\n            closure\n        };\n\n        // push the env\n        let closure_env = Module::from_exprs(closure.env);\n        self.root_mod.module.stack_push(NS_PARAM, closure_env);\n        let closure = Box::new(Func {\n            env: HashMap::new(),\n            ..*closure\n        });\n\n        if log::log_enabled!(log::Level::Debug) {\n            let name = closure\n                .name_hint\n                .clone()\n                .unwrap_or_else(|| Ident::from_name(\"<unnamed>\"));\n            log::debug!(\"resolving args of function {name}\");\n        }\n        let res = self.resolve_function_args(closure)?;\n\n        let closure = match res {\n            Ok(func) => func,\n            Err(func) => {\n                return Ok(*expr_of_func(func, span));\n            }\n        };\n\n        let needs_window = (closure.params.last())\n            .and_then(|p| p.ty.as_ref())\n            .map(types::is_sub_type_of_array)\n            .unwrap_or_default();\n\n        // evaluate\n        let res = if let ExprKind::Internal(operator_name) = &closure.body.kind {\n            // special case: functions that have internal body\n\n            if operator_name.starts_with(\"std.\") {\n                Expr {\n                    ty: closure.return_ty,\n                    needs_window,\n                    ..Expr::new(ExprKind::RqOperator {\n                        name: operator_name.clone(),\n                        args: closure.args,\n                    })\n                }\n            } else {\n                let expr = self.resolve_special_func(closure, needs_window)?;\n                self.fold_expr(expr)?\n            }\n        } else {\n            // base case: materialize\n            self.materialize_function(closure)?\n        };\n\n        // pop the env\n        self.root_mod.module.stack_pop(NS_PARAM).unwrap();\n\n        Ok(Expr { span, ..res })\n    }\n\n    #[allow(clippy::boxed_local)]\n    fn materialize_function(&mut self, closure: Box<Func>) -> Result<Expr> {\n        log::debug!(\"stack_push for {}\", closure.as_debug_name());\n\n        let (func_env, body, return_ty) = env_of_closure(*closure);\n\n        self.root_mod.module.stack_push(NS_PARAM, func_env);\n\n        // fold again, to resolve inner variables & functions\n        let body = self.fold_expr(body)?;\n\n        // remove param decls\n        log::debug!(\"stack_pop: {:?}\", body.id);\n        let func_env = self.root_mod.module.stack_pop(NS_PARAM).unwrap();\n\n        Ok(if let ExprKind::Func(mut inner_closure) = body.kind {\n            // body couldn't been resolved - construct a closure to be evaluated later\n\n            inner_closure.env = func_env.into_exprs();\n\n            // Get the missing params (params that don't have args yet)\n            let missing = inner_closure.params[inner_closure.args.len()..].to_vec();\n\n            // Create wrapper params and add references to them as args to the inner closure\n            let mut wrapper_params = Vec::with_capacity(missing.len());\n            for (i, param) in missing.iter().enumerate() {\n                let param_name = format!(\"_partial_{i}\");\n                let substitute_arg = Expr::new(Ident::from_path(vec![\n                    NS_PARAM.to_string(),\n                    param_name.clone(),\n                ]));\n                inner_closure.args.push(substitute_arg);\n                wrapper_params.push(FuncParam {\n                    name: param_name,\n                    ty: param.ty.clone(),\n                    default_value: None,\n                });\n            }\n\n            Expr::new(ExprKind::Func(Box::new(Func {\n                name_hint: None,\n                args: vec![],\n                params: wrapper_params,\n                body: Box::new(Expr::new(ExprKind::Func(inner_closure))),\n\n                // these don't matter\n                named_params: Default::default(),\n                return_ty: Default::default(),\n                env: Default::default(),\n            })))\n        } else {\n            // resolved, return result\n\n            // make sure to use the resolved type\n            let mut body = body;\n            if let Some(ret_ty) = return_ty.map(|x| *x) {\n                body.ty = Some(ret_ty.clone());\n            }\n\n            body\n        })\n    }\n\n    /// Folds function types, so they are resolved to material types, ready for type checking.\n    pub fn fold_function_types(&mut self, mut func: Box<Func>) -> Result<Box<Func>> {\n        func.params = func\n            .params\n            .into_iter()\n            .map(|p| -> Result<_> {\n                Ok(FuncParam {\n                    ty: fold_type_opt(self, p.ty)?,\n                    ..p\n                })\n            })\n            .try_collect()?;\n        func.return_ty = fold_type_opt(self, func.return_ty)?;\n        Ok(func)\n    }\n\n    pub fn apply_args_to_closure(\n        &mut self,\n        mut closure: Box<Func>,\n        args: Vec<Expr>,\n        mut named_args: HashMap<String, Expr>,\n    ) -> Result<Box<Func>> {\n        // named arguments are consumed only by the first function\n\n        // named\n        for mut param in closure.named_params.drain(..) {\n            let param_name = param.name.split('.').next_back().unwrap_or(&param.name);\n            let default = param.default_value.take().unwrap();\n\n            let arg = named_args.remove(param_name).unwrap_or(*default);\n\n            closure.args.push(arg);\n            closure.params.insert(closure.args.len() - 1, param);\n        }\n        if let Some((name, _)) = named_args.into_iter().next() {\n            // TODO: report all remaining named_args as separate errors\n            return Err(Error::new_simple(format!(\n                \"unknown named argument `{name}` to closure {:?}\",\n                closure.name_hint\n            )));\n        }\n\n        // positional\n        closure.args.extend(args);\n        Ok(closure)\n    }\n\n    /// Resolves function arguments. Will return `Err(func)` is partial application is required.\n    fn resolve_function_args(\n        &mut self,\n        #[allow(clippy::boxed_local)] to_resolve: Box<Func>,\n    ) -> Result<Result<Box<Func>, Box<Func>>> {\n        let mut closure = Box::new(Func {\n            args: vec![Expr::new(Literal::Null); to_resolve.args.len()],\n            ..*to_resolve\n        });\n        let mut partial_application_position = None;\n\n        let func_name = &closure.name_hint;\n\n        let (relations, other): (Vec<_>, Vec<_>) = zip(&closure.params, to_resolve.args)\n            .enumerate()\n            .partition(|(_, (param, _))| {\n                let is_relation = param\n                    .ty\n                    .as_ref()\n                    .map(|t| t.is_relation())\n                    .unwrap_or_default();\n\n                is_relation\n            });\n\n        let has_relations = !relations.is_empty();\n\n        // resolve relational args\n        if has_relations {\n            self.root_mod.module.shadow(NS_THIS);\n            self.root_mod.module.shadow(NS_THAT);\n\n            // First, resolve all relational arguments\n            let mut resolved_relations = Vec::new();\n            for (pos, (index, (param, mut arg))) in relations.into_iter().with_position() {\n                let is_last = matches!(pos, Position::Last | Position::Only);\n\n                // just fold the argument alone\n                if partial_application_position.is_none() {\n                    arg = self\n                        .fold_and_type_check(arg, param, func_name)?\n                        .unwrap_or_else(|a| {\n                            partial_application_position = Some(index);\n                            a\n                        });\n                }\n                log::debug!(\"resolved arg to {}\", arg.kind.as_ref());\n\n                resolved_relations.push((index, arg, is_last));\n            }\n\n            // Then, add relation frames into scope\n            for (index, arg, is_last) in resolved_relations {\n                if partial_application_position.is_none() {\n                    let frame = arg.lineage.as_ref().ok_or_else(|| {\n                        // Provide helpful error for empty arrays/tuples used directly\n                        // (not from functions like std.from_text which set lineage properly)\n                        match &arg.kind {\n                            ExprKind::Array(v) if v.is_empty() => Error::new(Reason::Expected {\n                                who: None,\n                                expected: \"a table or query\".to_string(),\n                                found: \"an empty array `[]`\".to_string(),\n                            })\n                            .with_span(arg.span),\n                            ExprKind::Tuple(v) if v.is_empty() => Error::new(Reason::Expected {\n                                who: None,\n                                expected: \"a table or query\".to_string(),\n                                found: \"an empty tuple `{}`\".to_string(),\n                            })\n                            .with_span(arg.span),\n                            _ => Error::new_bug(4317).with_span(closure.body.span),\n                        }\n                    })?;\n                    if is_last {\n                        self.root_mod.module.insert_frame(frame, NS_THIS);\n                    } else {\n                        self.root_mod.module.insert_frame(frame, NS_THAT);\n                    }\n                }\n\n                closure.args[index] = arg;\n            }\n        }\n\n        // resolve other positional\n        for (index, (param, mut arg)) in other {\n            if partial_application_position.is_none() {\n                if let ExprKind::Tuple(fields) = arg.kind {\n                    // if this is a tuple, resolve elements separately,\n                    // so they can be added to scope, before resolving subsequent elements.\n\n                    let mut fields_new = Vec::with_capacity(fields.len());\n                    for field in fields {\n                        let field = self.fold_within_namespace(field, &param.name)?;\n\n                        // add aliased columns into scope\n                        if let Some(alias) = field.alias.clone() {\n                            let id = field.id.unwrap();\n                            self.root_mod.module.insert_frame_col(NS_THIS, alias, id);\n                        }\n                        fields_new.push(field);\n                    }\n\n                    // note that this tuple node has to be resolved itself\n                    // (it's elements are already resolved and so their resolving\n                    // should be skipped)\n                    arg.kind = ExprKind::Tuple(fields_new);\n                }\n\n                arg = self\n                    .fold_and_type_check(arg, param, func_name)?\n                    .unwrap_or_else(|a| {\n                        partial_application_position = Some(index);\n                        a\n                    });\n            }\n\n            closure.args[index] = arg;\n        }\n\n        if has_relations {\n            self.root_mod.module.unshadow(NS_THIS);\n            self.root_mod.module.unshadow(NS_THAT);\n        }\n\n        Ok(if let Some(position) = partial_application_position {\n            log::debug!(\n                \"partial application of {} at arg {position}\",\n                closure.as_debug_name()\n            );\n\n            Err(extract_partial_application(closure, position))\n        } else {\n            Ok(closure)\n        })\n    }\n\n    fn fold_and_type_check(\n        &mut self,\n        arg: Expr,\n        param: &FuncParam,\n        func_name: &Option<Ident>,\n    ) -> Result<Result<Expr, Expr>> {\n        let mut arg = self.fold_within_namespace(arg, &param.name)?;\n\n        // don't validate types of unresolved exprs\n        if arg.id.is_some() {\n            // validate type\n\n            let expects_func = param\n                .ty\n                .as_ref()\n                .map(|t| t.kind.is_function())\n                .unwrap_or_default();\n            if !expects_func && arg.kind.is_func() {\n                return Ok(Err(arg));\n            }\n\n            let who = || {\n                func_name\n                    .as_ref()\n                    .map(|n| format!(\"function {n}, param `{}`\", param.name))\n            };\n            self.validate_expr_type(&mut arg, param.ty.as_ref(), &who)?;\n        }\n\n        Ok(Ok(arg))\n    }\n\n    fn fold_within_namespace(&mut self, expr: Expr, param_name: &str) -> Result<Expr> {\n        let prev_namespace = self.default_namespace.take();\n\n        if param_name.starts_with(\"noresolve.\") {\n            return Ok(expr);\n        } else if let Some((ns, _)) = param_name.split_once('.') {\n            self.default_namespace = Some(ns.to_string());\n        } else {\n            self.default_namespace = None;\n        };\n\n        let res = self.fold_expr(expr);\n        self.default_namespace = prev_namespace;\n        res\n    }\n}\n\nfn extract_partial_application(mut func: Box<Func>, position: usize) -> Box<Func> {\n    // Input:\n    // Func {\n    //     params: [x, y, z],\n    //     args: [\n    //         x,\n    //         Func {\n    //             params: [a, b],\n    //             args: [a],\n    //             body: arg_body\n    //         },\n    //         z\n    //     ],\n    //     body: parent_body\n    // }\n\n    // Output:\n    // Func {\n    //     params: [b],\n    //     args: [],\n    //     body: Func {\n    //         params: [x, y, z],\n    //         args: [\n    //             x,\n    //             Func {\n    //                 params: [a, b],\n    //                 args: [a, b],\n    //                 body: arg_body\n    //             },\n    //             z\n    //         ],\n    //         body: parent_body\n    //     }\n    // }\n\n    // This is quite in-efficient, especially for long pipelines.\n    // Maybe it could be special-cased, for when the arg func has a single param.\n    // In that case, it may be possible to pull the arg func up and basically swap\n    // it with the parent func.\n\n    let arg = func.args.get_mut(position).unwrap();\n    let arg_func = arg.kind.as_func_mut().unwrap();\n\n    let param_name = format!(\"_partial_{}\", arg.id.unwrap());\n    let substitute_arg = Expr::new(Ident::from_path(vec![\n        NS_PARAM.to_string(),\n        param_name.clone(),\n    ]));\n    arg_func.args.push(substitute_arg);\n\n    // set the arg func body to the parent func\n    Box::new(Func {\n        name_hint: None,\n        return_ty: None,\n        body: Box::new(Expr::new(ExprKind::Func(func))),\n        params: vec![FuncParam {\n            name: param_name,\n            ty: None,\n            default_value: None,\n        }],\n        named_params: Default::default(),\n        args: Default::default(),\n        env: Default::default(),\n    })\n}\n\nfn env_of_closure(closure: Func) -> (Module, Expr, Option<Box<Ty>>) {\n    let mut func_env = Module::default();\n\n    for (param, arg) in zip(closure.params, closure.args) {\n        let v = Decl {\n            declared_at: arg.id,\n            kind: DeclKind::Expr(Box::new(arg)),\n            ..Default::default()\n        };\n        let param_name = param.name.split('.').next_back().unwrap();\n        func_env.names.insert(param_name.to_string(), v);\n    }\n\n    (func_env, *closure.body, closure.return_ty.map(Box::new))\n}\n\npub fn expr_of_func(func: Box<Func>, span: Option<Span>) -> Box<Expr> {\n    let ty = TyFunc {\n        params: func\n            .params\n            .iter()\n            .skip(func.args.len())\n            .map(|a| a.ty.clone())\n            .collect(),\n        return_ty: func\n            .return_ty\n            .clone()\n            .or_else(|| func.clone().body.ty)\n            .map(Box::new),\n        name_hint: func.name_hint.clone(),\n    };\n\n    Box::new(Expr {\n        ty: Some(Ty::new(ty)),\n        span,\n        ..Expr::new(ExprKind::Func(func))\n    })\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/inference.rs",
    "content": "use itertools::Itertools;\n\nuse super::Resolver;\nuse crate::ir::decl::{Decl, TableDecl, TableExpr};\nuse crate::ir::pl::{Lineage, LineageColumn, LineageInput};\nuse crate::pr::{Ident, Ty, TyTupleField};\nuse crate::semantic::{NS_DEFAULT_DB, NS_INFER};\nuse crate::Result;\n\nimpl Resolver<'_> {\n    pub fn infer_table_column(\n        &mut self,\n        table_ident: &Ident,\n        col_name: &str,\n    ) -> Result<(), String> {\n        let table = self.root_mod.module.get_mut(table_ident).unwrap();\n        let table_decl = table.kind.as_table_decl_mut().unwrap();\n\n        let Some(columns) = table_decl.ty.as_mut().and_then(|t| t.as_relation_mut()) else {\n            return Err(format!(\"Variable {table_ident:?} is not a relation.\"));\n        };\n\n        let has_wildcard = columns\n            .iter()\n            .any(|c| matches!(c, TyTupleField::Wildcard(_)));\n        if !has_wildcard {\n            return Err(format!(\"Table {table_ident:?} does not have wildcard.\"));\n        }\n\n        let exists = columns.iter().any(|c| match c {\n            TyTupleField::Single(Some(n), _) => n == col_name,\n            _ => false,\n        });\n        if exists {\n            return Ok(());\n        }\n\n        columns.push(TyTupleField::Single(Some(col_name.to_string()), None));\n\n        // also add into input tables of this table expression\n        if let TableExpr::RelationVar(expr) = &table_decl.expr {\n            if let Some(frame) = &expr.lineage {\n                let wildcard_inputs = (frame.columns.iter())\n                    .filter_map(|c| c.as_all())\n                    .collect_vec();\n\n                match wildcard_inputs.len() {\n                    0 => return Err(format!(\"Cannot infer where {table_ident}.{col_name} is from\")),\n                    1 => {\n                        let (input_id, _) = wildcard_inputs.into_iter().next().unwrap();\n\n                        // input_id comes from LineageColumn::All in frame.columns.\n                        // Should be valid, but if this panics, see #5280 and lowering.rs\n                        // for the pattern where columns reference out-of-scope inputs.\n                        let input = frame.find_input(*input_id).unwrap();\n                        let table_ident = input.table.clone();\n                        self.infer_table_column(&table_ident, col_name)?;\n                    }\n                    _ => {\n                        return Err(format!(\"Cannot infer where {table_ident}.{col_name} is from. It could be any of {wildcard_inputs:?}\"))\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Converts a identifier that points to a table declaration to lineage of that table.\n    pub fn lineage_of_table_decl(\n        &mut self,\n        table_fq: &Ident,\n        input_name: String,\n        input_id: usize,\n    ) -> Lineage {\n        let table_decl = self.root_mod.module.get(table_fq).unwrap();\n        let TableDecl { ty, expr } = table_decl.kind.as_table_decl().unwrap();\n\n        // For CTEs (RelationVar), trace lineage back to the underlying source tables.\n        // For UNIONs and JOINs, this includes all underlying source tables.\n        let underlying_inputs = match expr {\n            TableExpr::RelationVar(rel) => rel.lineage.as_ref().map(|l| &l.inputs),\n            _ => None,\n        };\n\n        let inputs = match underlying_inputs {\n            Some(inputs) if !inputs.is_empty() => inputs\n                .iter()\n                .map(|inp| LineageInput {\n                    id: input_id,\n                    name: input_name.clone(),\n                    table: inp.table.clone(),\n                })\n                .collect(),\n            _ => vec![LineageInput {\n                id: input_id,\n                name: input_name.clone(),\n                table: table_fq.clone(),\n            }],\n        };\n\n        // TODO: can this panic?\n        let columns = ty.as_ref().unwrap().as_relation().unwrap();\n\n        let mut instance_frame = Lineage {\n            inputs,\n            columns: Vec::new(),\n            ..Default::default()\n        };\n\n        for col in columns {\n            let col = match col {\n                TyTupleField::Wildcard(_) => LineageColumn::All {\n                    input_id,\n                    except: columns\n                        .iter()\n                        .flat_map(|c| c.as_single().map(|x| x.0).cloned().flatten())\n                        .collect(),\n                },\n                TyTupleField::Single(col_name, _) => LineageColumn::Single {\n                    name: col_name\n                        .clone()\n                        .map(|col_name| Ident::from_path(vec![input_name.clone(), col_name])),\n                    target_id: input_id,\n                    target_name: col_name.clone(),\n                },\n            };\n            instance_frame.columns.push(col);\n        }\n\n        log::debug!(\"instanced table {table_fq} as {instance_frame:?}\");\n        instance_frame\n    }\n\n    /// Declares a new table for a relation literal.\n    /// This is needed for column inference to work properly.\n    pub(super) fn declare_table_for_literal(\n        &mut self,\n        input_id: usize,\n        columns: Option<Vec<TyTupleField>>,\n        name_hint: Option<String>,\n    ) -> Lineage {\n        log::debug!(\"declare_table_for_literal: {input_id:?} {columns:?} {name_hint:?}\");\n\n        let id = input_id;\n        let global_name = format!(\"_literal_{id}\");\n\n        // declare a new table in the `default_db` module\n        let default_db_ident = Ident::from_name(NS_DEFAULT_DB);\n        let default_db = self.root_mod.module.get_mut(&default_db_ident).unwrap();\n        let default_db = default_db.kind.as_module_mut().unwrap();\n\n        let infer_default = default_db.get(&Ident::from_name(NS_INFER)).unwrap().clone();\n        let mut infer_default = *infer_default.kind.into_infer().unwrap();\n\n        let table_decl = infer_default.as_table_decl_mut().unwrap();\n        table_decl.expr = TableExpr::None;\n\n        if let Some(columns) = columns {\n            table_decl.ty = Some(Ty::relation(columns));\n        }\n\n        default_db\n            .names\n            .insert(global_name.clone(), Decl::from(infer_default));\n\n        // produce a frame of that table\n        let input_name = name_hint.unwrap_or_else(|| global_name.clone());\n        let table_fq = default_db_ident + Ident::from_name(global_name);\n        self.lineage_of_table_decl(&table_fq, input_name, id)\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/mod.rs",
    "content": "use crate::ir::decl::RootModule;\nuse crate::utils::IdGenerator;\n\nmod expr;\nmod flatten;\nmod functions;\nmod inference;\nmod names;\nmod static_eval;\nmod stmt;\nmod transforms;\nmod types;\n\n/// Can fold (walk) over AST and for each function call or variable find what they are referencing.\npub struct Resolver<'a> {\n    root_mod: &'a mut RootModule,\n\n    current_module_path: Vec<String>,\n\n    default_namespace: Option<String>,\n\n    /// Sometimes ident closures must be resolved and sometimes not. See [test::test_func_call_resolve].\n    in_func_call_name: bool,\n\n    pub id: IdGenerator<usize>,\n}\n\n#[derive(Default, Clone)]\npub struct ResolverOptions {}\n\nimpl Resolver<'_> {\n    pub fn new(root_mod: &mut RootModule) -> Resolver<'_> {\n        Resolver {\n            root_mod,\n            current_module_path: Vec::new(),\n            default_namespace: None,\n            in_func_call_name: false,\n            id: IdGenerator::new(),\n        }\n    }\n}\n\n#[cfg(test)]\npub(super) mod test {\n    use insta::assert_yaml_snapshot;\n\n    use crate::ir::pl::{Expr, Lineage, PlFold};\n    use crate::{Errors, Result};\n\n    pub fn erase_ids(expr: Expr) -> Expr {\n        IdEraser {}.fold_expr(expr).unwrap()\n    }\n\n    struct IdEraser {}\n\n    impl PlFold for IdEraser {\n        fn fold_expr(&mut self, mut expr: Expr) -> Result<Expr> {\n            expr.kind = self.fold_expr_kind(expr.kind)?;\n            expr.id = None;\n            expr.target_id = None;\n            Ok(expr)\n        }\n    }\n\n    fn parse_and_resolve(query: &str) -> Result<Expr, Errors> {\n        let ctx = crate::semantic::test::parse_and_resolve(query)?;\n        let (main, _) = ctx.find_main_rel(&[]).unwrap();\n        Ok(*main.clone().into_relation_var().unwrap())\n    }\n\n    fn resolve_lineage(query: &str) -> Result<Lineage, Errors> {\n        Ok(parse_and_resolve(query)?.lineage.unwrap())\n    }\n\n    fn resolve_derive(query: &str) -> Result<Vec<Expr>, Errors> {\n        let expr = parse_and_resolve(query)?;\n        let derive = expr.kind.into_transform_call().unwrap();\n        let exprs = derive\n            .kind\n            .into_derive()\n            .unwrap_or_else(|e| panic!(\"Failed to convert `{e:?}`\"))\n            .kind\n            .into_tuple()\n            .unwrap_or_else(|e| panic!(\"Failed to convert `{e:?}`\"));\n\n        let exprs = IdEraser {}.fold_exprs(exprs).unwrap();\n        Ok(exprs)\n    }\n\n    #[test]\n    fn test_variables_1() {\n        assert_yaml_snapshot!(resolve_derive(\n            r#\"\n            from employees\n            derive {\n                gross_salary = salary + payroll_tax,\n                gross_cost =   gross_salary + benefits_cost\n            }\n            \"#\n        )\n        .unwrap());\n    }\n\n    #[test]\n    #[ignore]\n    fn test_non_existent_function() {\n        // `myfunc` is a valid reference to a column and\n        // a column can be a function, right?\n        // If not, how would we express that with type system?\n        parse_and_resolve(r#\"from mytable | filter (myfunc col1)\"#).unwrap_err();\n    }\n\n    #[test]\n    fn test_functions_1() {\n        assert_yaml_snapshot!(resolve_derive(\n            r#\"\n            let subtract = a b -> a - b\n\n            from employees\n            derive {\n                net_salary = subtract gross_salary tax\n            }\n            \"#\n        )\n        .unwrap());\n    }\n\n    #[test]\n    fn test_functions_nested() {\n        assert_yaml_snapshot!(resolve_derive(\n            r#\"\n            let lag_day = x -> s\"lag_day_todo({x})\"\n            let ret = x dividend_return ->  x / (lag_day x) - 1 + dividend_return\n\n            from a\n            derive (ret b c)\n            \"#\n        )\n        .unwrap());\n    }\n\n    #[test]\n    fn test_functions_pipeline() {\n        assert_yaml_snapshot!(resolve_derive(\n            r#\"\n            from a\n            derive one = (foo | sum)\n            \"#\n        )\n        .unwrap());\n\n        assert_yaml_snapshot!(resolve_derive(\n            r#\"\n            let plus_one = x -> x + 1\n            let plus = x y -> x + y\n\n            from a\n            derive {b = (sum foo | plus_one | plus 2)}\n            \"#\n        )\n        .unwrap());\n    }\n    #[test]\n    fn test_named_args() {\n        assert_yaml_snapshot!(resolve_derive(\n            r#\"\n            let add_one = x to:1 -> x + to\n\n            from foo_table\n            derive {\n                added = add_one bar to:3,\n                added_default = add_one bar\n            }\n            \"#\n        )\n        .unwrap());\n    }\n\n    #[test]\n    fn test_frames_and_names() {\n        assert_yaml_snapshot!(resolve_lineage(\n            r#\"\n            from orders\n            select {customer_no, gross, tax, gross - tax}\n            take 20\n            \"#\n        )\n        .unwrap());\n\n        assert_yaml_snapshot!(resolve_lineage(\n            r#\"\n            from table_1\n            join customers (==customer_no)\n            \"#\n        )\n        .unwrap());\n\n        assert_yaml_snapshot!(resolve_lineage(\n            r#\"\n            from e = employees\n            join salaries (==emp_no)\n            group {e.emp_no, e.gender} (\n                aggregate {\n                    emp_salary = average salaries.salary\n                }\n            )\n            \"#\n        )\n        .unwrap());\n    }\n\n    // Helper function to verify basic lineage structure after append\n    fn verify_append_lineage_basics(\n        final_lineage: &crate::ir::pl::Lineage,\n        expected_inputs: &[&str],\n    ) {\n        let input_names: Vec<&str> = final_lineage\n            .inputs\n            .iter()\n            .map(|i| i.name.as_str())\n            .collect();\n\n        for expected_input in expected_inputs {\n            assert!(input_names.contains(expected_input));\n            assert!(final_lineage.find_input_by_name(expected_input).is_some());\n        }\n\n        assert!(!final_lineage.columns.is_empty());\n        for col in &final_lineage.columns {\n            match col {\n                crate::ir::pl::LineageColumn::Single {\n                    name, target_id, ..\n                } => {\n                    assert!(target_id > &0);\n                    assert!(name.is_some());\n                }\n                crate::ir::pl::LineageColumn::All { .. } => {}\n            }\n        }\n    }\n\n    // Helper function to find source frames by input name\n    fn find_source_frames<'a>(\n        fc: &'a crate::semantic::reporting::FrameCollector,\n        top_input_name: &str,\n        bottom_input_name: &str,\n    ) -> (\n        Option<&'a crate::ir::pl::Lineage>,\n        Option<&'a crate::ir::pl::Lineage>,\n    ) {\n        let mut top_frame = None;\n        let mut bottom_frame = None;\n\n        for (_span, frame) in &fc.frames {\n            if frame.inputs.len() == 1 {\n                let input_name = &frame.inputs[0].name;\n                if input_name == top_input_name && top_frame.is_none() {\n                    top_frame = Some(frame);\n                } else if input_name == bottom_input_name && bottom_frame.is_none() {\n                    bottom_frame = Some(frame);\n                }\n            }\n        }\n\n        (top_frame, bottom_frame)\n    }\n\n    // Helper function to verify column-level lineage for Single columns\n    fn verify_single_column_lineage(\n        final_lineage: &crate::ir::pl::Lineage,\n        fc: &crate::semantic::reporting::FrameCollector,\n        top_frame: &crate::ir::pl::Lineage,\n        bottom_frame: &crate::ir::pl::Lineage,\n    ) {\n        assert_eq!(final_lineage.columns.len(), top_frame.columns.len());\n        assert_eq!(final_lineage.columns.len(), bottom_frame.columns.len());\n\n        for ((union_col, top_col), bottom_col) in final_lineage\n            .columns\n            .iter()\n            .zip(top_frame.columns.iter())\n            .zip(bottom_frame.columns.iter())\n        {\n            if let (\n                crate::ir::pl::LineageColumn::Single { .. },\n                crate::ir::pl::LineageColumn::Single {\n                    name: top_name,\n                    target_id: top_target_id,\n                    ..\n                },\n                crate::ir::pl::LineageColumn::Single {\n                    name: bottom_name,\n                    target_id: bottom_target_id,\n                    ..\n                },\n            ) = (union_col, top_col, bottom_col)\n            {\n                if let (Some(top_name), Some(bottom_name)) = (top_name, bottom_name) {\n                    assert_eq!(top_name.name, bottom_name.name);\n                }\n\n                assert!(fc.nodes.iter().any(|n| n.id == *top_target_id));\n                assert!(fc.nodes.iter().any(|n| n.id == *bottom_target_id));\n            }\n        }\n\n        for col in &final_lineage.columns {\n            if let crate::ir::pl::LineageColumn::Single { target_id, .. } = col {\n                assert!(fc.nodes.iter().any(|n| n.id == *target_id));\n            }\n        }\n    }\n\n    // Helper function to verify expression graph contains all expected nodes\n    fn verify_expression_graph_nodes(\n        fc: &crate::semantic::reporting::FrameCollector,\n        final_lineage: &crate::ir::pl::Lineage,\n        top_frame: &crate::ir::pl::Lineage,\n        bottom_frame: &crate::ir::pl::Lineage,\n    ) {\n        for input in &final_lineage.inputs {\n            assert!(fc.nodes.iter().any(|n| n.id == input.id));\n        }\n\n        let top_col_target_ids: Vec<usize> = top_frame\n            .columns\n            .iter()\n            .filter_map(|c| match c {\n                crate::ir::pl::LineageColumn::Single { target_id, .. } => Some(*target_id),\n                _ => None,\n            })\n            .collect();\n\n        let bottom_col_target_ids: Vec<usize> = bottom_frame\n            .columns\n            .iter()\n            .filter_map(|c| match c {\n                crate::ir::pl::LineageColumn::Single { target_id, .. } => Some(*target_id),\n                _ => None,\n            })\n            .collect();\n\n        for target_id in &top_col_target_ids {\n            assert!(fc.nodes.iter().any(|n| n.id == *target_id));\n        }\n\n        for target_id in &bottom_col_target_ids {\n            assert!(fc.nodes.iter().any(|n| n.id == *target_id));\n        }\n    }\n\n    #[test]\n    fn test_append_union_different_tables() {\n        // This test verifies that lineage tracking for append/union operations\n        // correctly tracks inputs from both tables and shows column-level lineage.\n        use crate::internal::pl_to_lineage;\n\n        let query = r#\"\n        from employees\n        select { name, salary }\n        append (\n            from managers\n            select { name, salary }\n        )\n        \"#;\n\n        let pl = crate::prql_to_pl(query).unwrap();\n        let fc = pl_to_lineage(pl).unwrap();\n        let final_lineage = &fc.frames.last().unwrap().1;\n\n        assert_yaml_snapshot!(final_lineage);\n\n        verify_append_lineage_basics(final_lineage, &[\"employees\", \"managers\"]);\n\n        let (top_frame, bottom_frame) = find_source_frames(&fc, \"employees\", \"managers\");\n        let top_frame = top_frame.unwrap();\n        let bottom_frame = bottom_frame.unwrap();\n\n        verify_single_column_lineage(final_lineage, &fc, top_frame, bottom_frame);\n\n        let employees_input = final_lineage.find_input_by_name(\"employees\").unwrap();\n        let managers_input = final_lineage.find_input_by_name(\"managers\").unwrap();\n\n        assert!(final_lineage\n            .inputs\n            .iter()\n            .any(|inp| inp.id == employees_input.id));\n        assert!(final_lineage\n            .inputs\n            .iter()\n            .any(|inp| inp.id == managers_input.id));\n\n        verify_expression_graph_nodes(&fc, final_lineage, top_frame, bottom_frame);\n    }\n\n    #[test]\n    fn test_append_union_same_table_with_exclude() {\n        // This test attempts to exercise the All columns path by unioning\n        // the same table with itself using select with exclude.\n        use crate::internal::pl_to_lineage;\n\n        let query = r#\"\n        from employees\n        select !{name}\n        append (\n            from employees\n            select !{salary}\n        )\n        \"#;\n\n        let pl = crate::prql_to_pl(query).unwrap();\n        let fc = pl_to_lineage(pl).unwrap();\n        let final_lineage = &fc.frames.last().unwrap().1;\n\n        verify_append_lineage_basics(final_lineage, &[\"employees\"]);\n    }\n\n    #[test]\n    fn test_append_union_all_columns_same_input() {\n        // This test exercises the All columns path with same input_id (lines 765-766)\n        // to ensure code coverage for merging except sets when both All columns\n        // come from the same input.\n        use crate::ir::pl::{\n            Expr, ExprKind, Lineage, LineageColumn, LineageInput, TransformCall, TransformKind,\n        };\n        use std::collections::HashSet;\n\n        let input = LineageInput {\n            id: 100,\n            name: \"employees\".to_string(),\n            table: crate::ir::pl::Ident {\n                path: vec![\"default_db\".to_string()],\n                name: \"employees\".to_string(),\n            },\n        };\n\n        let mut top_lineage = Lineage::default();\n        top_lineage.inputs.push(input.clone());\n        top_lineage.columns.push(LineageColumn::All {\n            input_id: 100,\n            except: {\n                let mut set = HashSet::new();\n                set.insert(\"name\".to_string());\n                set\n            },\n        });\n\n        let mut bottom_lineage = Lineage::default();\n        bottom_lineage.inputs.push(input.clone());\n        bottom_lineage.columns.push(LineageColumn::All {\n            input_id: 100,\n            except: {\n                let mut set = HashSet::new();\n                set.insert(\"salary\".to_string());\n                set\n            },\n        });\n\n        let mut top_expr = Expr::new(ExprKind::Ident(crate::ir::pl::Ident::from_name(\"top\")));\n        top_expr.lineage = Some(top_lineage);\n\n        let mut bottom_expr = Expr::new(ExprKind::Ident(crate::ir::pl::Ident::from_name(\"bottom\")));\n        bottom_expr.lineage = Some(bottom_lineage);\n\n        let transform_call = TransformCall {\n            kind: Box::new(TransformKind::Append(Box::new(bottom_expr))),\n            input: Box::new(top_expr),\n            partition: None,\n            frame: crate::ir::pl::WindowFrame::default(),\n            sort: Vec::new(),\n        };\n\n        let result = transform_call.infer_lineage().unwrap();\n\n        match &result.columns[0] {\n            LineageColumn::All { input_id, except } => {\n                assert_eq!(*input_id, 100);\n                assert!(except.contains(\"name\"));\n                assert!(except.contains(\"salary\"));\n            }\n            _ => panic!(\"Expected All column\"),\n        }\n    }\n\n    #[test]\n    fn test_cte_lineage_traces_to_source_table() {\n        // This test verifies that simple CTEs trace lineage back to\n        // the underlying source table instead of showing the CTE name.\n        use crate::internal::pl_to_lineage;\n\n        let query = r#\"\n        let employees_usa = (from employees | filter country == \"USA\")\n        from employees_usa\n        select {name, salary}\n        \"#;\n\n        let pl = crate::prql_to_pl(query).unwrap();\n        let fc = pl_to_lineage(pl).unwrap();\n        let final_lineage = &fc.frames.last().unwrap().1;\n\n        assert_eq!(\n            final_lineage.inputs.len(),\n            1,\n            \"Simple CTE should have 1 input, got {:?}\",\n            final_lineage.inputs\n        );\n\n        let input = &final_lineage.inputs[0];\n        assert_eq!(\n            input.name, \"employees_usa\",\n            \"Input name should be the CTE alias\"\n        );\n        assert_eq!(\n            input.table.name, \"employees\",\n            \"Table should trace back to source table 'employees', got {:?}\",\n            input.table\n        );\n    }\n\n    #[test]\n    fn test_direct_table_lineage_uses_table_itself() {\n        // This test verifies that direct table references (non-CTEs)\n        // use the table itself as the lineage input, exercising the\n        // fallback path in lineage_of_table_decl.\n        use crate::internal::pl_to_lineage;\n\n        let query = r#\"\n        from employees\n        select {name, salary}\n        \"#;\n\n        let pl = crate::prql_to_pl(query).unwrap();\n        let fc = pl_to_lineage(pl).unwrap();\n        let final_lineage = &fc.frames.last().unwrap().1;\n\n        assert_eq!(\n            final_lineage.inputs.len(),\n            1,\n            \"Direct table should have 1 input\"\n        );\n\n        let input = &final_lineage.inputs[0];\n        assert_eq!(\n            input.table.name, \"employees\",\n            \"Table should be 'employees' directly\"\n        );\n    }\n\n    #[test]\n    fn test_cte_lineage_with_union_traces_to_all_source_tables() {\n        // This test verifies that CTEs with UNIONs trace lineage\n        // back to ALL underlying source tables.\n        use crate::internal::pl_to_lineage;\n\n        let query = r#\"\n        let combined = (\n            from employees\n            select {name, dept}\n            append (\n                from contractors\n                select {name, dept}\n            )\n        )\n        from combined\n        select {name}\n        \"#;\n\n        let pl = crate::prql_to_pl(query).unwrap();\n        let fc = pl_to_lineage(pl).unwrap();\n        let final_lineage = &fc.frames.last().unwrap().1;\n\n        // Should have inputs from both employees and contractors\n        assert_eq!(\n            final_lineage.inputs.len(),\n            2,\n            \"CTE with UNION should have 2 inputs, got {:?}\",\n            final_lineage.inputs\n        );\n\n        let tables: Vec<_> = final_lineage\n            .inputs\n            .iter()\n            .map(|inp| inp.table.name.as_str())\n            .collect();\n\n        assert!(\n            tables.contains(&\"employees\"),\n            \"Should contain employees table, got {:?}\",\n            tables\n        );\n        assert!(\n            tables.contains(&\"contractors\"),\n            \"Should contain contractors table, got {:?}\",\n            tables\n        );\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/names.rs",
    "content": "use std::collections::HashSet;\n\nuse itertools::Itertools;\n\nuse super::Resolver;\nuse crate::ir::decl::{Decl, DeclKind, Module};\nuse crate::ir::pl::{Expr, ExprKind};\nuse crate::pr::Ident;\nuse crate::semantic::{NS_INFER, NS_INFER_MODULE, NS_SELF, NS_THAT, NS_THIS};\nuse crate::Error;\nuse crate::Result;\nuse crate::WithErrorInfo;\n\nimpl Resolver<'_> {\n    pub(super) fn resolve_ident(&mut self, ident: &Ident) -> Result<Ident, Error> {\n        let mut res = if let Some(default_namespace) = self.default_namespace.clone() {\n            self.resolve_ident_core(ident, Some(&default_namespace))\n        } else {\n            let mut ident = ident.clone().prepend(self.current_module_path.clone());\n\n            let mut res = self.resolve_ident_core(&ident, None);\n            for _ in 0..self.current_module_path.len() {\n                if res.is_ok() {\n                    break;\n                }\n                ident = ident.pop_front().1.unwrap();\n                res = self.resolve_ident_core(&ident, None);\n            }\n            res\n        };\n\n        match &res {\n            Ok(fq_ident) => {\n                let decl = self.root_mod.module.get(fq_ident).unwrap();\n                if let DeclKind::Import(target) = &decl.kind {\n                    let target = target.clone();\n                    return self.resolve_ident(&target);\n                }\n            }\n            Err(e) => {\n                log::debug!(\n                    \"cannot resolve `{ident}`: `{e:?}`, root_mod={:#?}\",\n                    self.root_mod\n                );\n\n                // attach available names\n                let mut available_names = Vec::new();\n                available_names.extend(self.collect_columns_in_module(NS_THIS));\n                available_names.extend(self.collect_columns_in_module(NS_THAT));\n                if !available_names.is_empty() {\n                    let available_names = available_names.iter().map(Ident::to_string).join(\", \");\n                    res = res.push_hint(format!(\"available columns: {available_names}\"));\n                }\n            }\n        }\n        res\n    }\n\n    fn collect_columns_in_module(&mut self, mod_name: &str) -> Vec<Ident> {\n        let mut cols = Vec::new();\n\n        let Some(module) = self.root_mod.module.names.get(mod_name) else {\n            return cols;\n        };\n\n        let DeclKind::Module(this) = &module.kind else {\n            return cols;\n        };\n\n        for (ident, decl) in this.as_decls().into_iter().sorted_by_key(|x| x.1.order) {\n            if let DeclKind::Column(_) = decl.kind {\n                cols.push(ident);\n            }\n        }\n        cols\n    }\n\n    pub(super) fn resolve_ident_core(\n        &mut self,\n        ident: &Ident,\n        default_namespace: Option<&String>,\n    ) -> Result<Ident, Error> {\n        // special case: wildcard\n        if ident.name == \"*\" {\n            // TODO: we may want to raise an error if someone has passed `download*` in\n            // an attempt to query for all `download` columns and expects to be able\n            // to select a `download_2020_01_01` column later in the query. But\n            // sometimes we want to query for `*.parquet` files, and give them an\n            // alias. So we don't raise an error here, but if there's a way of\n            // differentiating the cases, we can implement that.\n            // if ident.name != \"*\" {\n            //     return Err(\"Unsupported feature: advanced wildcard column matching\".to_string());\n            // }\n\n            // For bare `*` (no prefix), prepend the default namespace so it\n            // resolves like `this.*` within the current context.\n            let wildcard_ident = match (ident.path.is_empty(), default_namespace) {\n                (true, Some(ns)) => ident.clone().prepend(vec![ns.clone()]),\n                _ => ident.clone(),\n            };\n            return self\n                .resolve_ident_wildcard(&wildcard_ident)\n                .map_err(Error::new_simple);\n        }\n\n        // base case: direct lookup\n        let decls = self.root_mod.module.lookup(ident);\n        match decls.len() {\n            // no match: try match *\n            0 => {}\n\n            // single match, great!\n            1 => return Ok(decls.into_iter().next().unwrap()),\n\n            // ambiguous\n            _ => return Err(ambiguous_error(decls, None)),\n        }\n\n        let ident = if let Some(default_namespace) = default_namespace {\n            let ident = ident.clone().prepend(vec![default_namespace.clone()]);\n\n            let decls = self.root_mod.module.lookup(&ident);\n            match decls.len() {\n                // no match: try match *\n                0 => ident,\n\n                // single match, great!\n                1 => return Ok(decls.into_iter().next().unwrap()),\n\n                // ambiguous\n                _ => return Err(ambiguous_error(decls, None)),\n            }\n        } else {\n            ident.clone()\n        };\n\n        // fallback case: try to match with NS_INFER and infer the declaration\n        // from the original ident.\n        match self.resolve_ident_fallback(&ident, NS_INFER) {\n            // The declaration and all needed parent modules were created\n            // -> just return the fq ident\n            Ok(inferred_ident) => Ok(inferred_ident),\n\n            // Was not able to infer.\n            Err(None) => Err(Error::new_simple(\n                format!(\"Unknown name `{}`\", &ident).to_string(),\n            )),\n            Err(Some(msg)) => Err(msg),\n        }\n    }\n\n    /// Try lookup of the ident with name replaced. If unsuccessful, recursively retry parent ident.\n    fn resolve_ident_fallback(\n        &mut self,\n        ident: &Ident,\n        name_replacement: &'static str,\n    ) -> Result<Ident, Option<Error>> {\n        let infer_ident = ident.clone().with_name(name_replacement);\n\n        // lookup of infer_ident\n        let mut decls = self.root_mod.module.lookup(&infer_ident);\n\n        if decls.is_empty() {\n            if let Some(parent) = infer_ident.clone().pop() {\n                // try to infer parent\n                let _ = self.resolve_ident_fallback(&parent, NS_INFER_MODULE)?;\n\n                // module was successfully inferred, retry the lookup\n                decls = self.root_mod.module.lookup(&infer_ident)\n            }\n        }\n\n        match decls.len() {\n            1 => {\n                // single match, great!\n                let infer_ident = decls.into_iter().next().unwrap();\n                self.infer_decl(infer_ident, ident)\n                    .map_err(|x| Some(Error::new_simple(x)))\n            }\n            0 => Err(None),\n            _ => Err(Some(ambiguous_error(decls, Some(&ident.name)))),\n        }\n    }\n\n    /// Create a declaration of [original] from template provided by declaration of [infer_ident].\n    fn infer_decl(&mut self, infer_ident: Ident, original: &Ident) -> Result<Ident, String> {\n        let infer = self.root_mod.module.get(&infer_ident).unwrap();\n        let mut infer_default = *infer.kind.as_infer().cloned().unwrap();\n\n        if let DeclKind::Module(new_module) = &mut infer_default {\n            // Modules are inferred only for database inference.\n            // Because we want to infer database modules that nested arbitrarily deep,\n            // we cannot store the template in DeclKind::Infer, but we override it here.\n            *new_module = Module::new_database();\n        }\n\n        let module_ident = infer_ident.pop().unwrap();\n        let module = self.root_mod.module.get_mut(&module_ident).unwrap();\n        let module = module.kind.as_module_mut().unwrap();\n\n        // insert default\n        module\n            .names\n            .insert(original.name.clone(), Decl::from(infer_default));\n\n        // infer table columns\n        if let Some(decl) = module.names.get(NS_SELF).cloned() {\n            if let DeclKind::InstanceOf(table_ident, _) = decl.kind {\n                log::debug!(\"inferring {original} to be from table {table_ident}\");\n                self.infer_table_column(&table_ident, &original.name)?;\n            }\n        }\n\n        Ok(module_ident + Ident::from_name(original.name.clone()))\n    }\n\n    fn resolve_ident_wildcard(&mut self, ident: &Ident) -> Result<Ident, String> {\n        let ident_self = ident.clone().pop().ok_or_else(|| {\n            \"Column wildcard `*` must be qualified, e.g. `table_name.*`\".to_string()\n        })? + Ident::from_name(NS_SELF);\n        let mut res = self.root_mod.module.lookup(&ident_self);\n        if res.contains(&ident_self) {\n            res = HashSet::from_iter([ident_self]);\n        }\n        if res.len() != 1 {\n            return Err(format!(\"Unknown relation {ident}\"));\n        }\n        let module_fq_self = res.into_iter().next().unwrap();\n\n        // Materialize into a tuple literal, containing idents.\n        let fields = self.construct_wildcard_include(&module_fq_self);\n\n        // This is just a workaround to return an Expr from this function.\n        // We wrap the expr into DeclKind::Expr and save it into the root module.\n        let cols_expr = Expr {\n            flatten: true,\n            ..Expr::new(ExprKind::Tuple(fields))\n        };\n        let cols_expr = DeclKind::Expr(Box::new(cols_expr));\n        let save_as = \"_wildcard_match\";\n        self.root_mod\n            .module\n            .names\n            .insert(save_as.to_string(), cols_expr.into());\n\n        // Then we can return ident to that decl.\n        Ok(Ident::from_name(save_as))\n    }\n}\n\nfn ambiguous_error(idents: HashSet<Ident>, replace_name: Option<&String>) -> Error {\n    let all_this = idents.iter().all(|d| d.starts_with_part(NS_THIS));\n\n    let mut chunks = Vec::new();\n    for mut ident in idents {\n        if all_this {\n            let (_, rem) = ident.pop_front();\n            if let Some(rem) = rem {\n                ident = rem;\n            } else {\n                continue;\n            }\n        }\n\n        if let Some(name) = replace_name {\n            ident.name.clone_from(name);\n        }\n        chunks.push(ident.to_string());\n    }\n    chunks.sort();\n    let hint = format!(\"could be any of: {}\", chunks.join(\", \"));\n    Error::new_simple(\"Ambiguous name\").push_hint(hint)\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__append_union_different_tables.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: final_lineage\n---\ncolumns:\n  - Single:\n      name:\n        - employees\n        - name\n      target_id: 132\n      target_name: ~\n  - Single:\n      name:\n        - employees\n        - salary\n      target_id: 133\n      target_name: ~\ninputs:\n  - id: 130\n    name: employees\n    table:\n      - default_db\n      - employees\n  - id: 119\n    name: managers\n    table:\n      - default_db\n      - managers\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__frames_and_names-2.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: \"resolve_lineage(r#\\\"\\n            from table_1\\n            join customers (==customer_no)\\n            \\\"#).unwrap()\"\n---\ncolumns:\n  - All:\n      input_id: 117\n      except: []\n  - All:\n      input_id: 114\n      except: []\ninputs:\n  - id: 117\n    name: table_1\n    table:\n      - default_db\n      - table_1\n  - id: 114\n    name: customers\n    table:\n      - default_db\n      - customers\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__frames_and_names-3.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: \"resolve_lineage(r#\\\"\\n            from e = employees\\n            join salaries (==emp_no)\\n            group {e.emp_no, e.gender} (\\n                aggregate {\\n                    emp_salary = average salaries.salary\\n                }\\n            )\\n            \\\"#).unwrap()\"\n---\ncolumns:\n  - Single:\n      name:\n        - e\n        - emp_no\n      target_id: 127\n      target_name: ~\n  - Single:\n      name:\n        - e\n        - gender\n      target_id: 128\n      target_name: ~\n  - Single:\n      name:\n        - emp_salary\n      target_id: 146\n      target_name: ~\ninputs:\n  - id: 120\n    name: e\n    table:\n      - default_db\n      - employees\n  - id: 117\n    name: salaries\n    table:\n      - default_db\n      - salaries\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__frames_and_names.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: \"resolve_lineage(r#\\\"\\n            from orders\\n            select {customer_no, gross, tax, gross - tax}\\n            take 20\\n            \\\"#).unwrap()\"\n---\ncolumns:\n  - Single:\n      name:\n        - orders\n        - customer_no\n      target_id: 121\n      target_name: ~\n  - Single:\n      name:\n        - orders\n        - gross\n      target_id: 122\n      target_name: ~\n  - Single:\n      name:\n        - orders\n        - tax\n      target_id: 123\n      target_name: ~\n  - Single:\n      name: ~\n      target_id: 124\n      target_name: ~\ninputs:\n  - id: 119\n    name: orders\n    table:\n      - default_db\n      - orders\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__functions_1.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: \"resolve_derive(r#\\\"\\n            let subtract = a b -> a - b\\n\\n            from employees\\n            derive {\\n                net_salary = subtract gross_salary tax\\n            }\\n            \\\"#).unwrap()\"\n---\n- RqOperator:\n    name: std.sub\n    args:\n      - Ident:\n          - this\n          - employees\n          - gross_salary\n        span: \"1:128-140\"\n      - Ident:\n          - this\n          - employees\n          - tax\n        span: \"1:141-144\"\n  span: \"1:119-144\"\n  alias: net_salary\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__functions_nested.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: \"resolve_derive(r#\\\"\\n            let lag_day = x -> s\\\"lag_day_todo({x})\\\"\\n            let ret = x dividend_return ->  x / (lag_day x) - 1 + dividend_return\\n\\n            from a\\n            derive (ret b c)\\n            \\\"#).unwrap()\"\n---\n- RqOperator:\n    name: std.add\n    args:\n      - RqOperator:\n          name: std.sub\n          args:\n            - RqOperator:\n                name: std.div_f\n                args:\n                  - Ident:\n                      - this\n                      - a\n                      - b\n                    span: \"1:179-180\"\n                  - SString:\n                      - String: lag_day_todo(\n                      - Expr:\n                          expr:\n                            Ident:\n                              - this\n                              - a\n                              - b\n                            span: \"1:179-180\"\n                          format: ~\n                      - String: )\n                    span: \"1:102-111\"\n              span: \"1:97-112\"\n            - Literal:\n                Integer: 1\n              span: \"1:115-116\"\n              ty:\n                kind:\n                  Primitive: Int\n                span: ~\n                name: ~\n        span: \"1:97-116\"\n      - Ident:\n          - this\n          - a\n          - c\n        span: \"1:181-182\"\n  span: \"1:175-182\"\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__functions_pipeline-2.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: \"resolve_derive(r#\\\"\\n            let plus_one = x -> x + 1\\n            let plus = x y -> x + y\\n\\n            from a\\n            derive {b = (sum foo | plus_one | plus 2)}\\n            \\\"#).unwrap()\"\n---\n- RqOperator:\n    name: std.add\n    args:\n      - Literal:\n          Integer: 2\n        span: \"1:146-147\"\n        ty:\n          kind:\n            Primitive: Int\n          span: ~\n          name: ~\n      - RqOperator:\n          name: std.add\n          args:\n            - RqOperator:\n                name: std.sum\n                args:\n                  - Ident:\n                      - this\n                      - a\n                      - foo\n                    span: \"1:124-127\"\n                    ty:\n                      kind:\n                        Array: ~\n                      span: \"0:1699-1701\"\n                      name: array\n              span: \"1:120-127\"\n            - Literal:\n                Integer: 1\n              span: \"1:37-38\"\n              ty:\n                kind:\n                  Primitive: Int\n                span: ~\n                name: ~\n        span: \"1:130-138\"\n  span: \"1:141-147\"\n  alias: b\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__functions_pipeline.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: \"resolve_derive(r#\\\"\\n            from a\\n            derive one = (foo | sum)\\n            \\\"#).unwrap()\"\n---\n- RqOperator:\n    name: std.sum\n    args:\n      - Ident:\n          - this\n          - a\n          - foo\n        span: \"1:46-49\"\n        ty:\n          kind:\n            Array: ~\n          span: \"0:1699-1701\"\n          name: array\n  span: \"1:52-55\"\n  alias: one\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__named_args.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: \"resolve_derive(r#\\\"\\n            let add_one = x to:1 -> x + to\\n\\n            from foo_table\\n            derive {\\n                added = add_one bar to:3,\\n                added_default = add_one bar\\n            }\\n            \\\"#).unwrap()\"\n---\n- RqOperator:\n    name: std.add\n    args:\n      - Ident:\n          - this\n          - foo_table\n          - bar\n        span: \"1:125-128\"\n      - Literal:\n          Integer: 3\n        span: \"1:132-133\"\n        ty:\n          kind:\n            Primitive: Int\n          span: ~\n          name: ~\n  span: \"1:117-133\"\n  alias: added\n- RqOperator:\n    name: std.add\n    args:\n      - Ident:\n          - this\n          - foo_table\n          - bar\n        span: \"1:175-178\"\n      - Literal:\n          Integer: 1\n        span: \"1:32-33\"\n        ty:\n          kind:\n            Primitive: Int\n          span: ~\n          name: ~\n  span: \"1:167-178\"\n  alias: added_default\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__test__variables_1.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/mod.rs\nexpression: \"resolve_derive(r#\\\"\\n            from employees\\n            derive {\\n                gross_salary = salary + payroll_tax,\\n                gross_cost =   gross_salary + benefits_cost\\n            }\\n            \\\"#).unwrap()\"\n---\n- RqOperator:\n    name: std.add\n    args:\n      - Ident:\n          - this\n          - employees\n          - salary\n        span: \"1:80-86\"\n      - Ident:\n          - this\n          - employees\n          - payroll_tax\n        span: \"1:89-100\"\n  span: \"1:80-100\"\n  alias: gross_salary\n- RqOperator:\n    name: std.add\n    args:\n      - Ident:\n          - this\n          - gross_salary\n        span: \"1:133-145\"\n      - Ident:\n          - this\n          - employees\n          - benefits_cost\n        span: \"1:148-161\"\n  span: \"1:133-161\"\n  alias: gross_cost\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/snapshots/prqlc__semantic__resolver__transforms__tests__aggregate_positional_arg-2.snap",
    "content": "---\nsource: prqlc/prqlc/src/semantic/resolver/transforms.rs\nexpression: expr\n---\nTransformCall:\n  input:\n    Ident:\n      - default_db\n      - c_invoice\n    span: \"1:9-23\"\n    ty:\n      kind:\n        Array:\n          kind:\n            Tuple:\n              - Wildcard: ~\n          span: \"0:1740-1744\"\n          name: ~\n      span: \"0:1739-1745\"\n      name: relation\n    lineage:\n      columns:\n        - All:\n            input_id: 116\n            except: []\n      inputs:\n        - id: 116\n          name: c_invoice\n          table:\n            - default_db\n            - c_invoice\n  kind:\n    Aggregate:\n      assigns:\n        Tuple:\n          - RqOperator:\n              name: std.average\n              args:\n                - Ident:\n                    - this\n                    - c_invoice\n                    - amount\n                  span: \"1:81-87\"\n                  ty:\n                    kind:\n                      Array: ~\n                    span: \"0:1699-1701\"\n                    name: array\n            span: \"1:73-87\"\n        span: \"1:73-87\"\n        ty:\n          kind:\n            Tuple:\n              - Single:\n                  - ~\n                  - ~\n          span: ~\n          name: ~\n  partition:\n    Tuple:\n      - Ident:\n          - this\n          - c_invoice\n          - issued_at\n        span: \"1:38-47\"\n    span: \"1:38-47\"\n    ty:\n      kind:\n        Tuple:\n          - Single:\n              - issued_at\n              - ~\n      span: ~\n      name: ~\nspan: \"1:62-88\"\nty:\n  kind:\n    Array:\n      kind:\n        Tuple:\n          - Single:\n              - issued_at\n              - ~\n          - Single:\n              - ~\n              - ~\n      span: ~\n      name: ~\n  span: ~\n  name: ~\nlineage:\n  columns:\n    - Single:\n        name:\n          - c_invoice\n          - issued_at\n        target_id: 118\n        target_name: ~\n    - Single:\n        name: ~\n        target_id: 134\n        target_name: ~\n  inputs:\n    - id: 116\n      name: c_invoice\n      table:\n        - default_db\n        - c_invoice\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/static_eval.rs",
    "content": "//! Static analysis - compile time expression evaluation\n\nuse crate::ir::pl::{Expr, ExprKind, Literal};\nuse crate::Result;\n\nimpl super::Resolver<'_> {\n    /// Tries to simplify this expression (and not child expressions) to a constant.\n    pub fn maybe_static_eval(&mut self, expr: Expr) -> Result<Expr> {\n        Ok(match &expr.kind {\n            ExprKind::RqOperator { .. } => {\n                let id = expr.id;\n                let span = expr.span;\n                let expr = static_eval_rq_operator(expr);\n                Expr { id, span, ..expr }\n            }\n\n            ExprKind::Case(_) => static_eval_case(expr),\n\n            _ => expr,\n        })\n    }\n}\n\nfn static_eval_rq_operator(mut expr: Expr) -> Expr {\n    let (name, mut args) = expr.kind.into_rq_operator().unwrap();\n    match name.as_str() {\n        \"std.not\" => {\n            if let ExprKind::Literal(Literal::Boolean(val)) = &args[0].kind {\n                return Expr::new(Literal::Boolean(!val));\n            }\n        }\n        \"std.neg\" => match &args[0].kind {\n            ExprKind::Literal(Literal::Integer(val)) => return Expr::new(Literal::Integer(-val)),\n            ExprKind::Literal(Literal::Float(val)) => return Expr::new(Literal::Float(-val)),\n            _ => (),\n        },\n\n        \"std.eq\" => {\n            if let (ExprKind::Literal(left), ExprKind::Literal(right)) =\n                (&args[0].kind, &args[1].kind)\n            {\n                // don't eval comparisons between different types of literals\n                if left.as_ref() == right.as_ref() {\n                    return Expr::new(Literal::Boolean(left == right));\n                }\n            }\n        }\n        \"std.ne\" => {\n            if let (ExprKind::Literal(left), ExprKind::Literal(right)) =\n                (&args[0].kind, &args[1].kind)\n            {\n                // don't eval comparisons between different types of literals\n                if left.as_ref() == right.as_ref() {\n                    return Expr::new(Literal::Boolean(left != right));\n                }\n            }\n        }\n        \"std.and\" => {\n            if let (\n                ExprKind::Literal(Literal::Boolean(left)),\n                ExprKind::Literal(Literal::Boolean(right)),\n            ) = (&args[0].kind, &args[1].kind)\n            {\n                return Expr::new(Literal::Boolean(*left && *right));\n            }\n        }\n        \"std.or\" => {\n            if let (\n                ExprKind::Literal(Literal::Boolean(left)),\n                ExprKind::Literal(Literal::Boolean(right)),\n            ) = (&args[0].kind, &args[1].kind)\n            {\n                return Expr::new(Literal::Boolean(*left || *right));\n            }\n        }\n        \"std.coalesce\" => {\n            if let ExprKind::Literal(Literal::Null) = &args[0].kind {\n                return args.remove(1);\n            }\n        }\n\n        _ => {}\n    };\n    expr.kind = ExprKind::RqOperator { name, args };\n    expr\n}\n\nfn static_eval_case(mut expr: Expr) -> Expr {\n    let items = expr.kind.into_case().unwrap();\n    let mut res = Vec::with_capacity(items.len());\n    for item in items {\n        if let ExprKind::Literal(Literal::Boolean(condition)) = item.condition.kind {\n            if condition {\n                res.push(item);\n                break;\n            } else {\n                // this case can be removed\n                continue;\n            }\n        } else {\n            res.push(item);\n        }\n    }\n    if res.is_empty() {\n        return Expr::new(Literal::Null);\n    }\n\n    if res.len() == 1 {\n        let is_true = matches!(\n            res[0].condition.kind,\n            ExprKind::Literal(Literal::Boolean(true))\n        );\n        if is_true {\n            return *res.remove(0).value;\n        }\n    }\n\n    expr.kind = ExprKind::Case(res);\n    expr\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/stmt.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::ir::decl::{Decl, DeclKind, Module, TableDecl, TableExpr};\nuse crate::ir::pl::*;\nuse crate::pr::{Ty, TyKind, TyTupleField};\nuse crate::Result;\nuse crate::WithErrorInfo;\n\nimpl super::Resolver<'_> {\n    // entry point to the resolver\n    pub fn fold_statements(&mut self, stmts: Vec<Stmt>) -> Result<()> {\n        for mut stmt in stmts {\n            stmt.id = Some(self.id.gen());\n            if let Some(span) = stmt.span {\n                self.root_mod.span_map.insert(stmt.id.unwrap(), span);\n            }\n\n            let ident = Ident {\n                path: self.current_module_path.clone(),\n                name: stmt.name().to_string(),\n            };\n\n            let mut def = match stmt.kind {\n                StmtKind::QueryDef(d) => {\n                    let decl = DeclKind::QueryDef(*d);\n                    self.root_mod\n                        .declare(ident, decl, stmt.id, Vec::new())\n                        .with_span(stmt.span)?;\n                    continue;\n                }\n                StmtKind::VarDef(var_def) => self.fold_var_def(var_def)?,\n                StmtKind::TypeDef(ty_def) => {\n                    let mut ty = self.fold_type(ty_def.value)?;\n                    ty.name = Some(ident.name.clone());\n\n                    let decl = DeclKind::Ty(ty);\n\n                    self.root_mod\n                        .declare(ident, decl, stmt.id, stmt.annotations)\n                        .with_span(stmt.span)?;\n                    continue;\n                }\n                StmtKind::ModuleDef(module_def) => {\n                    self.current_module_path.push(ident.name);\n\n                    let decl = Decl {\n                        declared_at: stmt.id,\n                        kind: DeclKind::Module(Module {\n                            names: HashMap::new(),\n                            redirects: Vec::new(),\n                            shadowed: None,\n                        }),\n                        annotations: stmt.annotations,\n                        ..Default::default()\n                    };\n                    let ident = Ident::from_path(self.current_module_path.clone());\n                    self.root_mod\n                        .module\n                        .insert(ident, decl)\n                        .with_span(stmt.span)?;\n\n                    self.fold_statements(module_def.stmts)?;\n                    self.current_module_path.pop();\n                    continue;\n                }\n                StmtKind::ImportDef(target) => {\n                    let decl = Decl {\n                        declared_at: stmt.id,\n                        kind: DeclKind::Import(target.name),\n                        annotations: stmt.annotations,\n                        ..Default::default()\n                    };\n\n                    self.root_mod\n                        .module\n                        .insert(ident, decl)\n                        .with_span(stmt.span)?;\n                    continue;\n                }\n            };\n\n            if def.name == \"main\" {\n                def.ty = Some(Ty::new(TyKind::Ident(Ident::from_path(vec![\n                    \"std\", \"relation\",\n                ]))));\n            }\n\n            if let Some(ExprKind::Func(closure)) = def.value.as_mut().map(|x| &mut x.kind) {\n                if closure.name_hint.is_none() {\n                    closure.name_hint = Some(ident.clone());\n                }\n            }\n\n            let expected_ty = fold_type_opt(self, def.ty)?;\n\n            let decl = match def.value {\n                Some(mut def_value) => {\n                    // var value is provided\n\n                    // validate type\n                    if expected_ty.is_some() {\n                        let who = || Some(def.name.clone());\n                        self.validate_expr_type(&mut def_value, expected_ty.as_ref(), &who)?;\n                    }\n\n                    prepare_expr_decl(def_value)\n                }\n                None => {\n                    // var value is not provided\n\n                    // is this a relation?\n                    if expected_ty.as_ref().is_some_and(|t| t.is_relation()) {\n                        // treat this var as a TableDecl\n                        DeclKind::TableDecl(TableDecl {\n                            ty: expected_ty,\n                            expr: TableExpr::LocalTable,\n                        })\n                    } else {\n                        // treat this var as a param\n                        let mut expr = Box::new(Expr::new(ExprKind::Param(def.name)));\n                        expr.ty = expected_ty;\n                        DeclKind::Expr(expr)\n                    }\n                }\n            };\n            self.root_mod\n                .declare(ident, decl, stmt.id, stmt.annotations)\n                .with_span(stmt.span)?;\n        }\n        Ok(())\n    }\n}\n\nfn prepare_expr_decl(value: Box<Expr>) -> DeclKind {\n    match &value.lineage {\n        Some(frame) => {\n            let columns = (frame.columns.iter())\n                .map(|col| match col {\n                    LineageColumn::All { .. } => TyTupleField::Wildcard(None),\n                    LineageColumn::Single { name, .. } => {\n                        TyTupleField::Single(name.as_ref().map(|n| n.name.clone()), None)\n                    }\n                })\n                .collect();\n            let ty = Some(Ty::relation(columns));\n\n            let expr = TableExpr::RelationVar(value);\n            DeclKind::TableDecl(TableDecl { ty, expr })\n        }\n        _ => DeclKind::Expr(value),\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/transforms.rs",
    "content": "use std::collections::HashMap;\nuse std::iter::zip;\n\nuse itertools::Itertools;\nuse serde::Deserialize;\n\nuse super::types::{ty_tuple_kind, type_intersection};\nuse super::Resolver;\nuse crate::ir::decl::{Decl, DeclKind, Module};\nuse crate::ir::generic::{SortDirection, WindowKind};\nuse crate::ir::pl::*;\nuse crate::pr::{Ty, TyKind, TyTupleField};\nuse crate::semantic::ast_expand::{restrict_null_literal, try_restrict_range};\nuse crate::semantic::resolver::functions::expr_of_func;\nuse crate::semantic::{write_pl, NS_PARAM, NS_THIS};\nuse crate::{compiler_version, Error, Reason, Result, WithErrorInfo};\n\nimpl Resolver<'_> {\n    /// try to convert function call with enough args into transform\n    #[allow(clippy::boxed_local)]\n    pub fn resolve_special_func(&mut self, func: Box<Func>, needs_window: bool) -> Result<Expr> {\n        let internal_name = func.body.kind.into_internal().unwrap();\n\n        let (kind, input) = match internal_name.as_str() {\n            \"select\" => {\n                let [assigns, tbl] = unpack::<2>(func.args);\n\n                let assigns = Box::new(self.coerce_into_tuple(assigns)?);\n                (TransformKind::Select { assigns }, tbl)\n            }\n            \"filter\" => {\n                let [filter, tbl] = unpack::<2>(func.args);\n\n                let filter = Box::new(filter);\n                (TransformKind::Filter { filter }, tbl)\n            }\n            \"derive\" => {\n                let [assigns, tbl] = unpack::<2>(func.args);\n\n                let assigns = Box::new(self.coerce_into_tuple(assigns)?);\n                (TransformKind::Derive { assigns }, tbl)\n            }\n            \"aggregate\" => {\n                let [assigns, tbl] = unpack::<2>(func.args);\n\n                let assigns = Box::new(self.coerce_into_tuple(assigns)?);\n                (TransformKind::Aggregate { assigns }, tbl)\n            }\n            \"sort\" => {\n                let [by, tbl] = unpack::<2>(func.args);\n\n                let by = self\n                    .coerce_into_tuple(by)?\n                    .try_cast(|x| x.into_tuple(), Some(\"sort\"), \"tuple\")?\n                    .into_iter()\n                    .map(|expr| {\n                        let (column, direction) = match expr.kind {\n                            ExprKind::RqOperator { name, mut args } if name == \"std.neg\" => {\n                                (args.remove(0), SortDirection::Desc)\n                            }\n                            _ => (expr, SortDirection::default()),\n                        };\n                        let column = Box::new(column);\n\n                        ColumnSort { direction, column }\n                    })\n                    .collect();\n\n                (TransformKind::Sort { by }, tbl)\n            }\n            \"take\" => {\n                let [expr, tbl] = unpack::<2>(func.args);\n\n                let range = if let ExprKind::Literal(Literal::Integer(n)) = expr.kind {\n                    range_from_ints(None, Some(n))\n                } else {\n                    match try_restrict_range(expr) {\n                        Ok((start, end)) => Range {\n                            start: restrict_null_literal(start).map(Box::new),\n                            end: restrict_null_literal(end).map(Box::new),\n                        },\n                        Err(expr) => {\n                            return Err(Error::new(Reason::Expected {\n                                who: Some(\"`take`\".to_string()),\n                                expected: \"int or range\".to_string(),\n                                found: write_pl(expr.clone()),\n                            })\n                            // Possibly this should refer to the item after the `take` where\n                            // one exists?\n                            .with_span(expr.span));\n                        }\n                    }\n                };\n\n                (TransformKind::Take { range }, tbl)\n            }\n            \"join\" => {\n                let [side, with, filter, tbl] = unpack::<4>(func.args);\n\n                let side = {\n                    let span = side.span;\n                    let ident =\n                        side.clone()\n                            .try_cast(ExprKind::into_ident, Some(\"side\"), \"ident\")?;\n\n                    // first try to match the raw ident string as a bare word\n                    match ident.to_string().as_str() {\n                        \"inner\" => JoinSide::Inner,\n                        \"left\" => JoinSide::Left,\n                        \"right\" => JoinSide::Right,\n                        \"full\" => JoinSide::Full,\n\n                        _ => {\n                            // if that fails, fold the ident and try treating the result as a literal\n                            // this allows the join side to be passed as a function parameter\n                            // NOTE: this is temporary, pending discussions and implementation, tracked in #4501\n                            let folded = self.fold_expr(side)?.try_cast(\n                                ExprKind::into_literal,\n                                Some(\"side\"),\n                                \"string literal\",\n                            )?;\n\n                            match folded.to_string().as_str() {\n                                \"\\\"inner\\\"\" => JoinSide::Inner,\n                                \"\\\"left\\\"\" => JoinSide::Left,\n                                \"\\\"right\\\"\" => JoinSide::Right,\n                                \"\\\"full\\\"\" => JoinSide::Full,\n\n                                _ => {\n                                    return Err(Error::new(Reason::Expected {\n                                        who: Some(\"`side`\".to_string()),\n                                        expected: \"inner, left, right or full\".to_string(),\n                                        found: folded.to_string(),\n                                    })\n                                    .with_span(span))\n                                }\n                            }\n                        }\n                    }\n                };\n\n                let filter = Box::new(filter);\n                let with = Box::new(with);\n                (TransformKind::Join { side, with, filter }, tbl)\n            }\n            \"group\" => {\n                let [by, pipeline, tbl] = unpack::<3>(func.args);\n\n                let by = Box::new(self.coerce_into_tuple(by)?);\n\n                // construct the relation that is passed into the pipeline\n                // (when generics are a thing, this can be removed)\n                let partition = {\n                    let partition = Expr::new(ExprKind::All {\n                        within: Box::new(Expr::new(Ident::from_name(NS_THIS))),\n                        except: by.clone(),\n                    });\n                    // wrap into select, so the names are resolved correctly\n                    let partition = FuncCall {\n                        name: Box::new(Expr::new(Ident::from_path(vec![\"std\", \"select\"]))),\n                        args: vec![partition, tbl],\n                        named_args: Default::default(),\n                    };\n                    let partition = Expr::new(ExprKind::FuncCall(partition));\n                    // fold, so lineage and types are inferred\n                    self.fold_expr(partition)?\n                };\n                let pipeline = self.fold_by_simulating_eval(pipeline, &partition)?;\n\n                // unpack tbl back out\n                let tbl = *partition.kind.into_transform_call().unwrap().input;\n\n                let pipeline = Box::new(pipeline);\n                (TransformKind::Group { by, pipeline }, tbl)\n            }\n            \"window\" => {\n                let [rows, range, expanding, rolling, pipeline, tbl] = unpack::<6>(func.args);\n\n                let expanding = {\n                    let as_bool = expanding.kind.as_literal().and_then(|l| l.as_boolean());\n\n                    *as_bool.ok_or_else(|| {\n                        Error::new(Reason::Expected {\n                            who: Some(\"parameter `expanding`\".to_string()),\n                            expected: \"a boolean\".to_string(),\n                            found: write_pl(expanding.clone()),\n                        })\n                        .with_span(expanding.span)\n                    })?\n                };\n\n                let rolling = {\n                    let as_int = rolling.kind.as_literal().and_then(|x| x.as_integer());\n\n                    *as_int.ok_or_else(|| {\n                        Error::new(Reason::Expected {\n                            who: Some(\"parameter `rolling`\".to_string()),\n                            expected: \"a number\".to_string(),\n                            found: write_pl(rolling.clone()),\n                        })\n                        .with_span(rolling.span)\n                    })?\n                };\n\n                let rows = {\n                    let range_tuple = try_restrict_range(rows).map_err(|expr| {\n                        Error::new(Reason::Expected {\n                            who: Some(\"parameter `rows`\".to_string()),\n                            expected: \"a range\".to_string(),\n                            found: write_pl(expr.clone()),\n                        })\n                        .with_span(expr.span)\n                    })?;\n                    into_literal_range(range_tuple)?\n                };\n\n                let range = {\n                    let range_tuple = try_restrict_range(range).map_err(|expr| {\n                        Error::new(Reason::Expected {\n                            who: Some(\"parameter `range`\".to_string()),\n                            expected: \"a range\".to_string(),\n                            found: write_pl(expr.clone()),\n                        })\n                        .with_span(expr.span)\n                    })?;\n                    into_literal_range(range_tuple)?\n                };\n\n                let (kind, start, end) = if expanding {\n                    (WindowKind::Rows, None, Some(0))\n                } else if rolling > 0 {\n                    (WindowKind::Rows, Some(-rolling + 1), Some(0))\n                } else if !range_is_empty(&rows) {\n                    (WindowKind::Rows, rows.0, rows.1)\n                } else if !range_is_empty(&range) {\n                    (WindowKind::Range, range.0, range.1)\n                } else {\n                    (WindowKind::Rows, None, None)\n                };\n                // let start = Expr::new(start.map_or(Literal::Null, Literal::Integer));\n                // let end = Expr::new(end.map_or(Literal::Null, Literal::Integer));\n                let range = Range {\n                    start: start.map(Literal::Integer).map(Expr::new).map(Box::new),\n                    end: end.map(Literal::Integer).map(Expr::new).map(Box::new),\n                };\n\n                let pipeline = self.fold_by_simulating_eval(pipeline, &tbl)?;\n\n                let transform_kind = TransformKind::Window {\n                    kind,\n                    range,\n                    pipeline: Box::new(pipeline),\n                };\n                (transform_kind, tbl)\n            }\n            \"append\" => {\n                let [bottom, top] = unpack::<2>(func.args);\n\n                (TransformKind::Append(Box::new(bottom)), top)\n            }\n            \"loop\" => {\n                let [pipeline, tbl] = unpack::<2>(func.args);\n\n                let pipeline = self.fold_by_simulating_eval(pipeline, &tbl)?;\n\n                (TransformKind::Loop(Box::new(pipeline)), tbl)\n            }\n\n            \"in\" => {\n                // yes, this is not a transform, but this is the most appropriate place for it\n\n                let [pattern, value] = unpack::<2>(func.args);\n\n                if pattern.ty.as_ref().is_some_and(|x| x.kind.is_array()) {\n                    return Ok(Expr::new(ExprKind::RqOperator {\n                        name: \"std.array_in\".to_string(),\n                        args: vec![value, pattern],\n                    }));\n                }\n\n                let pattern = match try_restrict_range(pattern) {\n                    Ok((start, end)) => {\n                        let start = restrict_null_literal(start);\n                        let end = restrict_null_literal(end);\n\n                        let start = start.map(|s| new_binop(value.clone(), &[\"std\", \"gte\"], s));\n                        let end = end.map(|e| new_binop(value, &[\"std\", \"lte\"], e));\n\n                        let res = maybe_binop(start, &[\"std\", \"and\"], end);\n                        let res = res.unwrap_or_else(|| {\n                            Expr::new(ExprKind::Literal(Literal::Boolean(true)))\n                        });\n                        return Ok(res);\n                    }\n                    Err(expr) => expr,\n                };\n\n                return Err(Error::new(Reason::Expected {\n                    who: Some(\"std.in\".to_string()),\n                    expected: \"a pattern\".to_string(),\n                    found: write_pl(pattern.clone()),\n                })\n                .with_span(pattern.span));\n            }\n\n            \"tuple_every\" => {\n                // yes, this is not a transform, but this is the most appropriate place for it\n\n                let [list] = unpack::<1>(func.args);\n                let list = list.kind.into_tuple().unwrap();\n\n                let mut res = None;\n                for item in list {\n                    res = maybe_binop(res, &[\"std\", \"and\"], Some(item));\n                }\n                let res =\n                    res.unwrap_or_else(|| Expr::new(ExprKind::Literal(Literal::Boolean(true))));\n\n                return Ok(res);\n            }\n\n            \"tuple_map\" => {\n                // yes, this is not a transform, but this is the most appropriate place for it\n\n                let [func, list] = unpack::<2>(func.args);\n                let list_items = list.kind.into_tuple().unwrap();\n\n                let list_items = list_items\n                    .into_iter()\n                    .map(|item| {\n                        Expr::new(ExprKind::FuncCall(FuncCall::new_simple(\n                            func.clone(),\n                            vec![item],\n                        )))\n                    })\n                    .collect_vec();\n\n                return Ok(Expr {\n                    kind: ExprKind::Tuple(list_items),\n                    ..list\n                });\n            }\n\n            \"tuple_zip\" => {\n                // yes, this is not a transform, but this is the most appropriate place for it\n\n                let [a, b] = unpack::<2>(func.args);\n                let a = a.kind.into_tuple().unwrap();\n                let b = b.kind.into_tuple().unwrap();\n\n                let mut res = Vec::new();\n                for (a, b) in std::iter::zip(a, b) {\n                    res.push(Expr::new(ExprKind::Tuple(vec![a, b])));\n                }\n\n                return Ok(Expr::new(ExprKind::Tuple(res)));\n            }\n\n            \"_eq\" => {\n                // yes, this is not a transform, but this is the most appropriate place for it\n\n                let [list] = unpack::<1>(func.args);\n                let list = list.kind.into_tuple().unwrap();\n                let [a, b]: [Expr; 2] = list.try_into().unwrap();\n\n                let res = maybe_binop(Some(a), &[\"std\", \"eq\"], Some(b)).unwrap();\n                return Ok(res);\n            }\n\n            \"from_text\" => {\n                // yes, this is not a transform, but this is the most appropriate place for it\n\n                let [format, text_expr] = unpack::<2>(func.args);\n\n                let text = match text_expr.kind {\n                    ExprKind::Literal(Literal::String(text)) => text,\n                    _ => {\n                        return Err(Error::new(Reason::Expected {\n                            who: Some(\"std.from_text\".to_string()),\n                            expected: \"a string literal\".to_string(),\n                            found: format!(\"`{}`\", write_pl(text_expr.clone())),\n                        })\n                        .with_span(text_expr.span));\n                    }\n                };\n\n                let res = {\n                    let span = format.span;\n                    let format = format\n                        .try_cast(ExprKind::into_ident, Some(\"format\"), \"ident\")?\n                        .to_string();\n                    match format.as_str() {\n                        \"csv\" => from_text::parse_csv(&text)\n                            .map_err(|r| Error::new_simple(r).with_span(span))?,\n                        \"json\" => from_text::parse_json(&text)\n                            .map_err(|r| Error::new_simple(r).with_span(span))?,\n\n                        _ => {\n                            return Err(Error::new(Reason::Expected {\n                                who: Some(\"`format`\".to_string()),\n                                expected: \"csv or json\".to_string(),\n                                found: format,\n                            })\n                            .with_span(span))\n                        }\n                    }\n                };\n\n                let expr_id = text_expr.id.unwrap();\n                let input_name = text_expr.alias.unwrap_or_else(|| \"text\".to_string());\n\n                let columns: Vec<_> = res\n                    .columns\n                    .iter()\n                    .cloned()\n                    .map(|x| TyTupleField::Single(Some(x), None))\n                    .collect();\n\n                let frame =\n                    self.declare_table_for_literal(expr_id, Some(columns), Some(input_name));\n\n                let res = Expr::new(ExprKind::Array(\n                    res.rows\n                        .into_iter()\n                        .map(|row| {\n                            Expr::new(ExprKind::Tuple(\n                                row.into_iter()\n                                    .map(|lit| Expr::new(ExprKind::Literal(lit)))\n                                    .collect(),\n                            ))\n                        })\n                        .collect(),\n                ));\n                let res = Expr {\n                    lineage: Some(frame),\n                    id: text_expr.id,\n                    ..res\n                };\n                return Ok(res);\n            }\n\n            \"prql_version\" => {\n                // yes, this is not a transform, but this is the most appropriate place for it\n                let ver = compiler_version().to_string();\n                return Ok(Expr::new(ExprKind::Literal(Literal::String(ver))));\n            }\n\n            \"count\" | \"row_number\" => {\n                // HACK: these functions get `this`, resolved to `{x = {_self}}`, which\n                // throws an error during lowering.\n                // But because these functions don't *really* need an arg, we can just pass\n                // a null instead.\n                return Ok(Expr {\n                    needs_window,\n                    ..Expr::new(ExprKind::RqOperator {\n                        name: format!(\"std.{internal_name}\"),\n                        args: vec![Expr::new(Literal::Null)],\n                    })\n                });\n            }\n\n            _ => {\n                return Err(\n                    Error::new_simple(format!(\"unknown operator {internal_name}\"))\n                        .push_hint(\"this is a bug in prqlc\")\n                        .with_span(func.body.span),\n                )\n            }\n        };\n\n        let transform_call = TransformCall {\n            kind: Box::new(kind),\n            input: Box::new(input),\n            partition: None,\n            frame: WindowFrame::default(),\n            sort: Vec::new(),\n        };\n        let ty = self.infer_type_of_special_func(&transform_call)?;\n        Ok(Expr {\n            ty,\n            ..Expr::new(ExprKind::TransformCall(transform_call))\n        })\n    }\n\n    /// Wraps non-tuple Exprs into a singleton Tuple.\n    pub(super) fn coerce_into_tuple(&mut self, expr: Expr) -> Result<Expr> {\n        let is_tuple_ty =\n            expr.ty.as_ref().is_some_and(|t| t.kind.is_tuple()) && !expr.kind.is_all();\n        Ok(if is_tuple_ty {\n            // a helpful check for a common anti-pattern\n            if let Some(alias) = expr.alias {\n                return Err(Error::new(Reason::Unexpected {\n                    found: format!(\"assign to `{alias}`\"),\n                })\n                .push_hint(format!(\"move assign into the tuple: `[{alias} = ...]`\"))\n                .with_span(expr.span));\n            }\n\n            expr\n        } else {\n            let span = expr.span;\n            let mut expr = Expr::new(ExprKind::Tuple(vec![expr]));\n            expr.span = span;\n\n            self.fold_expr(expr)?\n        })\n    }\n\n    /// Figure out the type of a function call, if this function is a *special function*.\n    /// (declared in std module & requires special handling).\n    pub fn infer_type_of_special_func(\n        &mut self,\n        transform_call: &TransformCall,\n    ) -> Result<Option<Ty>> {\n        // Long term plan is to make this function obsolete with generic function parameters.\n        // In other words, I hope to make our type system powerful enough to express return\n        // type of all std module functions.\n\n        Ok(match transform_call.kind.as_ref() {\n            TransformKind::Select { assigns } => assigns\n                .ty\n                .clone()\n                .map(|x| Ty::new(TyKind::Array(Some(Box::new(x))))),\n            TransformKind::Derive { assigns } => {\n                let input = transform_call.input.ty.clone().unwrap();\n                let input = input.into_relation().unwrap();\n\n                let derived = assigns.ty.clone().unwrap();\n                let derived = derived.kind.into_tuple().unwrap();\n\n                Some(Ty::new(TyKind::Array(Some(Box::new(Ty::new(\n                    ty_tuple_kind([input, derived].concat()),\n                ))))))\n            }\n            TransformKind::Aggregate { assigns } => {\n                let tuple = assigns.ty.clone().unwrap();\n\n                Some(Ty::new(TyKind::Array(Some(Box::new(tuple)))))\n            }\n            TransformKind::Filter { .. }\n            | TransformKind::Sort { .. }\n            | TransformKind::Take { .. } => transform_call.input.ty.clone(),\n            TransformKind::Join { with, .. } => {\n                let input = transform_call.input.ty.clone().unwrap();\n                let input = input.into_relation().unwrap();\n\n                let with_name = with.alias.clone();\n                let with = with.ty.clone().unwrap();\n                let with = with.kind.into_array().unwrap().unwrap();\n                let with = TyTupleField::Single(with_name, Some(*with));\n\n                Some(Ty::new(TyKind::Array(Some(Box::new(Ty::new(\n                    ty_tuple_kind([input, vec![with]].concat()),\n                ))))))\n            }\n            TransformKind::Group { pipeline, by } => {\n                let by = by.ty.clone().unwrap();\n                let by = by.kind.into_tuple().unwrap();\n\n                let pipeline = pipeline.ty.clone().unwrap();\n                let pipeline = pipeline.kind.into_function().unwrap().unwrap();\n                let pipeline = pipeline.return_ty.unwrap().into_relation().unwrap();\n\n                Some(Ty::new(TyKind::Array(Some(Box::new(Ty::new(\n                    ty_tuple_kind([by, pipeline].concat()),\n                ))))))\n            }\n            TransformKind::Window { pipeline, .. } | TransformKind::Loop(pipeline) => {\n                let pipeline = pipeline.ty.clone().unwrap();\n                let pipeline = pipeline.kind.into_function().unwrap().unwrap();\n                pipeline.return_ty.map(|x| *x)\n            }\n            TransformKind::Append(bottom) => {\n                let top = transform_call.input.ty.clone().unwrap();\n                let bottom = bottom.ty.clone().unwrap();\n\n                Some(type_intersection(top, bottom))\n            }\n        })\n    }\n}\n\nfn range_is_empty(range: &(Option<i64>, Option<i64>)) -> bool {\n    match (&range.0, &range.1) {\n        (Some(s), Some(e)) => s > e,\n        _ => false,\n    }\n}\n\nfn range_from_ints(start: Option<i64>, end: Option<i64>) -> Range {\n    let start = start.map(|x| Box::new(Expr::new(ExprKind::Literal(Literal::Integer(x)))));\n    let end = end.map(|x| Box::new(Expr::new(ExprKind::Literal(Literal::Integer(x)))));\n    Range { start, end }\n}\n\nfn into_literal_range(range: (Expr, Expr)) -> Result<(Option<i64>, Option<i64>)> {\n    fn into_int(bound: Expr) -> Result<Option<i64>> {\n        match bound.kind {\n            ExprKind::Literal(Literal::Null) => Ok(None),\n            ExprKind::Literal(Literal::Integer(i)) => Ok(Some(i)),\n            _ => Err(Error::new_simple(\"expected an int literal\").with_span(bound.span)),\n        }\n    }\n    Ok((into_int(range.0)?, into_int(range.1)?))\n}\n\nimpl Resolver<'_> {\n    /// Simulate evaluation of the inner pipeline of group or window\n    // Creates a dummy node that acts as value that pipeline can be resolved upon.\n    fn fold_by_simulating_eval(&mut self, pipeline: Expr, val: &Expr) -> Result<Expr> {\n        log::debug!(\"fold by simulating evaluation\");\n        let span = pipeline.span;\n\n        let param_name = \"_tbl\";\n        let param_id = self.id.gen();\n\n        // resolver will not resolve a function call if any arguments are missing\n        // but would instead return a closure to be resolved later.\n        // because the pipeline of group is a function that takes a table chunk\n        // and applies the transforms to it, it would not get resolved.\n        // thats why we trick the resolver with a dummy node that acts as table\n        // chunk and instruct resolver to apply the transform on that.\n\n        let mut dummy = Expr::new(ExprKind::Ident(Ident::from_name(param_name)));\n        dummy.lineage.clone_from(&val.lineage);\n        dummy.ty.clone_from(&val.ty);\n\n        let pipeline = Expr::new(ExprKind::FuncCall(FuncCall::new_simple(\n            pipeline,\n            vec![dummy],\n        )));\n\n        let env = Module::singleton(param_name, Decl::from(DeclKind::Column(param_id)));\n        self.root_mod.module.stack_push(NS_PARAM, env);\n\n        let mut pipeline = self.fold_expr(pipeline)?;\n\n        // attach the span to the TransformCall, as this is what will\n        // be preserved after resolving is complete\n        pipeline.span = pipeline.span.or(span);\n\n        self.root_mod.module.stack_pop(NS_PARAM).unwrap();\n\n        // now, we need wrap the result into a closure and replace\n        // the dummy node with closure's parameter.\n\n        // validate that the return type is a relation\n        // this can be removed after we have proper type checking for all std functions\n        let expected = Some(Ty::relation(vec![TyTupleField::Wildcard(None)]));\n        self.validate_expr_type(&mut pipeline, expected.as_ref(), &|| {\n            Some(\"pipeline\".to_string())\n        })?;\n\n        // construct the function back\n        let func = Box::new(Func {\n            name_hint: None,\n            body: Box::new(pipeline),\n            return_ty: None,\n\n            args: vec![],\n            params: vec![FuncParam {\n                name: param_id.to_string(),\n                ty: None,\n                default_value: None,\n            }],\n            named_params: vec![],\n\n            env: Default::default(),\n        });\n        Ok(*expr_of_func(func, span))\n    }\n}\n\nimpl TransformCall {\n    pub fn infer_lineage(&self) -> Result<Lineage> {\n        use TransformKind::*;\n\n        fn lineage_or_default(expr: &Expr) -> Result<Lineage> {\n            expr.lineage.clone().ok_or_else(|| {\n                Error::new_simple(\"expected {expr:?} to have table type\").with_span(expr.span)\n            })\n        }\n\n        Ok(match self.kind.as_ref() {\n            Select { assigns } => {\n                let mut lineage = lineage_or_default(&self.input)?;\n\n                lineage.clear();\n                lineage.apply_assigns(assigns, false);\n                lineage\n            }\n            Derive { assigns } => {\n                let mut lineage = lineage_or_default(&self.input)?;\n\n                lineage.apply_assigns(assigns, false);\n                lineage\n            }\n            Group { pipeline, by, .. } => {\n                let mut lineage = lineage_or_default(&self.input)?;\n                lineage.clear();\n                lineage.apply_assigns(by, false);\n\n                // pipeline's body is resolved, just use its type\n                let Func { body, .. } = pipeline.kind.as_func().unwrap().as_ref();\n\n                let partition_lin = lineage_or_default(body).unwrap();\n                lineage.columns.extend(partition_lin.columns);\n\n                log::debug!(\".. type={lineage}\");\n                lineage\n            }\n            Window { pipeline, .. } => {\n                // pipeline's body is resolved, just use its type\n                let Func { body, .. } = pipeline.kind.as_func().unwrap().as_ref();\n\n                lineage_or_default(body).unwrap()\n            }\n            Aggregate { assigns } => {\n                let mut lineage = lineage_or_default(&self.input)?;\n                lineage.clear();\n\n                lineage.apply_assigns(assigns, false);\n                lineage\n            }\n            Join { with, .. } => {\n                let left = lineage_or_default(&self.input)?;\n                let right = lineage_or_default(with)?;\n                join(left, right)\n            }\n            Append(bottom) => {\n                let top = lineage_or_default(&self.input)?;\n                let bottom = lineage_or_default(bottom)?;\n                append(top, bottom)?\n            }\n            Loop(_) => lineage_or_default(&self.input)?,\n            Sort { .. } | Filter { .. } | Take { .. } => lineage_or_default(&self.input)?,\n        })\n    }\n}\n\nfn join(mut lhs: Lineage, rhs: Lineage) -> Lineage {\n    lhs.columns.extend(rhs.columns);\n    lhs.inputs.extend(rhs.inputs);\n    lhs\n}\n\nfn append(mut top: Lineage, bottom: Lineage) -> Result<Lineage, Error> {\n    if top.columns.len() != bottom.columns.len() {\n        return Err(Error::new_simple(\n            \"cannot append two relations with non-matching number of columns.\",\n        ))\n        .push_hint(format!(\n            \"top has {} columns, but bottom has {}\",\n            top.columns.len(),\n            bottom.columns.len()\n        ));\n    }\n\n    // Merge inputs from both relations so lineage can track both sources\n    // This is similar to how `join` handles inputs\n    top.inputs.extend(bottom.inputs);\n\n    // Merge columns positionally\n    // In a union, columns are aligned by position, so column 0 from top\n    // and column 0 from bottom become one output column\n    let mut columns = Vec::with_capacity(top.columns.len());\n    for (t, b) in zip(top.columns, bottom.columns) {\n        columns.push(match (t, b) {\n            // For All columns, keep the top's input_id but merge except sets\n            (\n                LineageColumn::All {\n                    input_id: input_id_t,\n                    except: except_t,\n                },\n                LineageColumn::All {\n                    input_id: _input_id_b,\n                    except: except_b,\n                },\n            ) => {\n                // Merge except sets from both tables\n                // This preserves exclusion information even when input_ids differ\n                // (e.g., \"from employees select !{name}\" append \"from managers select !{salary}\")\n                let mut except = except_t;\n                except.extend(except_b);\n\n                LineageColumn::All {\n                    input_id: input_id_t,\n                    except,\n                }\n            }\n            (\n                LineageColumn::Single {\n                    name: name_t,\n                    target_id,\n                    target_name,\n                },\n                LineageColumn::Single { name: name_b, .. },\n            ) => {\n                // For Single columns in a union, we keep the top's target_id\n                // Both inputs are now tracked in top.inputs, so lineage can\n                // reference either source even though the column only points to one\n                match (name_t, name_b) {\n                    (None, None) => LineageColumn::Single {\n                        name: None,\n                        target_id,\n                        target_name,\n                    },\n                    (None, Some(name)) | (Some(name), _) => LineageColumn::Single {\n                        name: Some(name),\n                        target_id,\n                        target_name,\n                    },\n                }\n            }\n            (t, b) => return Err(Error::new_simple(format!(\n                \"cannot match columns `{t:?}` and `{b:?}`\"\n            ))\n            .push_hint(\n                \"make sure that top and bottom relations of append has the same column layout\",\n            )),\n        });\n    }\n\n    top.columns = columns;\n    Ok(top)\n}\n\nimpl Lineage {\n    pub fn clear(&mut self) {\n        self.prev_columns.clear();\n        self.prev_columns.append(&mut self.columns);\n    }\n\n    pub fn apply_assigns(&mut self, assigns: &Expr, inline_refs: bool) {\n        match &assigns.kind {\n            ExprKind::Tuple(fields) => {\n                for expr in fields {\n                    self.apply_assigns(expr, inline_refs);\n                }\n\n                // hack for making `x | select { y = this }` work\n                if let Some(alias) = &assigns.alias {\n                    if self.columns.len() == 1 {\n                        let col = self.columns.first().unwrap();\n                        if let LineageColumn::All { input_id, .. } = col {\n                            let input = self.inputs.iter_mut().find(|i| i.id == *input_id).unwrap();\n                            input.name.clone_from(alias);\n                        }\n                    }\n                }\n            }\n            _ => self.apply_assign(assigns, inline_refs),\n        }\n    }\n\n    pub fn apply_assign(&mut self, expr: &Expr, inline_refs: bool) {\n        // special case: all except\n        if let ExprKind::All { within, except } = &expr.kind {\n            let mut within_lineage = Lineage::default();\n            within_lineage.inputs.extend(self.inputs.clone());\n            within_lineage.apply_assigns(within, true);\n\n            let mut except_lineage = Lineage::default();\n            except_lineage.inputs.extend(self.inputs.clone());\n            except_lineage.apply_assigns(except, true);\n\n            'within: for col in within_lineage.columns {\n                match col {\n                    LineageColumn::Single {\n                        ref name,\n                        ref target_id,\n                        ref target_name,\n                        ..\n                    } => {\n                        let is_excluded = except_lineage.columns.iter().any(|e| match e {\n                            LineageColumn::Single { name: e_name, .. } => name == e_name,\n\n                            LineageColumn::All {\n                                input_id: e_iid,\n                                except: e_except,\n                            } => {\n                                target_id == e_iid\n                                    && !e_except.contains(target_name.as_ref().unwrap())\n                            }\n                        });\n                        if !is_excluded {\n                            self.columns.push(col);\n                        }\n                    }\n                    LineageColumn::All {\n                        input_id,\n                        mut except,\n                    } => {\n                        for excluded in &except_lineage.columns {\n                            match excluded {\n                                LineageColumn::Single {\n                                    name: Some(name), ..\n                                } => {\n                                    // input_id comes from LineageColumn::All in self.columns,\n                                    // which should reference valid inputs in self.inputs.\n                                    // If this panics, it may indicate a scope issue similar to\n                                    // #5280 (join inside group) - see lowering.rs for the fix pattern.\n                                    let input = self.find_input(input_id).unwrap();\n                                    let ex_input_name = name.iter().next().unwrap();\n                                    if ex_input_name == &input.name {\n                                        except.insert(name.name.clone());\n                                    }\n                                }\n                                LineageColumn::Single { .. } => {}\n                                LineageColumn::All {\n                                    input_id: e_iid,\n                                    except: e_e,\n                                } => {\n                                    if *e_iid == input_id {\n                                        // The two `All`s match and will erase each other.\n                                        // The only remaining columns are those from the first wildcard\n                                        // that are not excluded, but are excluded in the second wildcard.\n                                        // Same assumption as above - input_id should be valid here.\n                                        let input = self.find_input(input_id).unwrap();\n                                        let input_name = input.name.clone();\n                                        for remaining in e_e.difference(&except).sorted() {\n                                            self.columns.push(LineageColumn::Single {\n                                                name: Some(Ident {\n                                                    path: vec![input_name.clone()],\n                                                    name: remaining.clone(),\n                                                }),\n                                                target_id: input_id,\n                                                target_name: Some(remaining.clone()),\n                                            })\n                                        }\n                                        continue 'within;\n                                    }\n                                }\n                            }\n                        }\n                        self.columns.push(LineageColumn::All { input_id, except });\n                    }\n                }\n            }\n            return;\n        }\n\n        // special case: include a tuple\n        if expr.ty.as_ref().is_some_and(|x| x.kind.is_tuple()) && expr.kind.is_ident() {\n            // this ident is a tuple, which means it much point to an input\n            let input_id = expr.target_id.unwrap();\n\n            self.columns.push(LineageColumn::All {\n                input_id,\n                except: Default::default(),\n            });\n            return;\n        }\n\n        // special case: an ref that should be inlined because this node\n        // might not exist in the resulting AST\n        if let Some(target_id) = expr.target_id.filter(|_| inline_refs) {\n            let ident = expr.kind.as_ident().unwrap().clone().pop_front().1.unwrap();\n            let input = &self.find_input(target_id);\n\n            self.columns.push(if input.is_some() {\n                LineageColumn::Single {\n                    target_name: Some(ident.name.clone()),\n                    name: Some(ident),\n                    target_id,\n                }\n            } else {\n                LineageColumn::Single {\n                    target_name: None,\n                    name: Some(ident),\n                    target_id,\n                }\n            });\n            return;\n        }\n\n        // base case: define the expr as a new lineage column\n        let (target_id, target_name) = (expr.id.unwrap(), None);\n\n        let alias = expr.alias.as_ref().map(Ident::from_name);\n        let name = alias.or_else(|| expr.kind.as_ident()?.clone().pop_front().1);\n\n        // remove names from columns with the same name\n        if name.is_some() {\n            for c in &mut self.columns {\n                if let LineageColumn::Single { name: n, .. } = c {\n                    if n.as_ref().map(|i| &i.name) == name.as_ref().map(|i| &i.name) {\n                        *n = None;\n                    }\n                }\n            }\n        }\n\n        self.columns.push(LineageColumn::Single {\n            name,\n            target_id,\n            target_name,\n        });\n    }\n\n    pub fn find_input_by_name(&self, input_name: &str) -> Option<&LineageInput> {\n        self.inputs.iter().find(|i| i.name == input_name)\n    }\n\n    pub fn find_input(&self, input_id: usize) -> Option<&LineageInput> {\n        self.inputs.iter().find(|i| i.id == input_id)\n    }\n\n    /// Renames all frame inputs to the given alias.\n    pub fn rename(&mut self, alias: String) {\n        for input in &mut self.inputs {\n            input.name.clone_from(&alias);\n        }\n\n        for col in &mut self.columns {\n            match col {\n                LineageColumn::All { .. } => {}\n                LineageColumn::Single {\n                    name: Some(name), ..\n                } => name.path = vec![alias.clone()],\n                _ => {}\n            }\n        }\n    }\n}\n\n/// Expects closure's args to be resolved.\n/// Note that named args are before positional args, in order of declaration.\nfn unpack<const P: usize>(func_args: Vec<Expr>) -> [Expr; P] {\n    func_args.try_into().expect(\"bad special function cast\")\n}\n\nmod from_text {\n    use super::*;\n    use crate::ir::rq::RelationLiteral;\n\n    // TODO: Can we dynamically get the types, like in pandas? We need to put\n    // quotes around strings and not around numbers.\n    // https://stackoverflow.com/questions/64369887/how-do-i-read-csv-data-without-knowing-the-structure-at-compile-time\n    pub fn parse_csv(text: &str) -> Result<RelationLiteral, String> {\n        let text = text.trim();\n        let mut rdr = csv::Reader::from_reader(text.as_bytes());\n\n        fn parse_header(row: &csv::StringRecord) -> Vec<String> {\n            row.into_iter().map(|x| x.to_string()).collect()\n        }\n\n        fn parse_row(row: csv::StringRecord) -> Vec<Literal> {\n            row.into_iter()\n                .map(|x| Literal::String(x.to_string()))\n                .collect()\n        }\n\n        Ok(RelationLiteral {\n            columns: parse_header(rdr.headers().map_err(|e| e.to_string())?),\n            rows: rdr\n                .records()\n                .map(|row_result| row_result.map(parse_row))\n                .try_collect()\n                .map_err(|e| e.to_string())?,\n        })\n    }\n\n    type JsonFormat1Row = HashMap<String, serde_json::Value>;\n\n    #[derive(Deserialize)]\n    struct JsonFormat2 {\n        columns: Vec<String>,\n        data: Vec<Vec<serde_json::Value>>,\n    }\n\n    fn map_json_primitive(primitive: serde_json::Value) -> Literal {\n        use serde_json::Value::*;\n        match primitive {\n            Null => Literal::Null,\n            Bool(bool) => Literal::Boolean(bool),\n            Number(number) if number.is_i64() => Literal::Integer(number.as_i64().unwrap()),\n            Number(number) if number.is_f64() => Literal::Float(number.as_f64().unwrap()),\n            Number(_) => Literal::Null,\n            String(string) => Literal::String(string),\n            Array(_) => Literal::Null,\n            Object(_) => Literal::Null,\n        }\n    }\n\n    fn object_to_vec(\n        mut row_map: HashMap<String, serde_json::Value>,\n        columns: &[String],\n    ) -> Vec<Literal> {\n        columns\n            .iter()\n            .map(|c| {\n                row_map\n                    .remove(c)\n                    .map(map_json_primitive)\n                    .unwrap_or(Literal::Null)\n            })\n            .collect_vec()\n    }\n\n    pub fn parse_json(text: &str) -> Result<RelationLiteral, String> {\n        parse_json1(text).or_else(|err1| {\n            parse_json2(text)\n                .map_err(|err2| format!(\"While parsing rows: {err1}\\nWhile parsing object: {err2}\"))\n        })\n    }\n\n    fn parse_json1(text: &str) -> Result<RelationLiteral, String> {\n        let data: Vec<JsonFormat1Row> = serde_json::from_str(text).map_err(|e| e.to_string())?;\n        let mut columns = data\n            .first()\n            .ok_or(\"json: no rows\")?\n            .keys()\n            .cloned()\n            .collect_vec();\n\n        // JSON object keys are not ordered, so have to apply some order to produce\n        // deterministic results\n        columns.sort();\n\n        let rows = data\n            .into_iter()\n            .map(|row_map| object_to_vec(row_map, &columns))\n            .collect_vec();\n        Ok(RelationLiteral { columns, rows })\n    }\n\n    fn parse_json2(text: &str) -> Result<RelationLiteral, String> {\n        let JsonFormat2 { columns, data } =\n            serde_json::from_str(text).map_err(|x| x.to_string())?;\n\n        Ok(RelationLiteral {\n            columns,\n            rows: data\n                .into_iter()\n                .map(|row| row.into_iter().map(map_json_primitive).collect_vec())\n                .collect_vec(),\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use insta::assert_yaml_snapshot;\n\n    use crate::semantic::test::parse_resolve_and_lower;\n\n    #[test]\n    fn test_aggregate_positional_arg() {\n        // distinct query #292\n\n        assert_yaml_snapshot!(parse_resolve_and_lower(\"\n        from c_invoice\n        select invoice_no\n        group invoice_no (\n            take 1\n        )\n        \").unwrap(), @r\"\n        def:\n          version: ~\n          other: {}\n        tables:\n          - id: 0\n            name: ~\n            relation:\n              kind:\n                ExternRef:\n                  LocalTable:\n                    - c_invoice\n              columns:\n                - Single: invoice_no\n                - Wildcard\n        relation:\n          kind:\n            Pipeline:\n              - From:\n                  source: 0\n                  columns:\n                    - - Single: invoice_no\n                      - 0\n                    - - Wildcard\n                      - 1\n                  name: c_invoice\n                  prefer_cte: true\n              - Select:\n                  - 0\n              - Take:\n                  range:\n                    start: ~\n                    end:\n                      kind:\n                        Literal:\n                          Integer: 1\n                      span: ~\n                  partition:\n                    - 0\n                  sort: []\n              - Select:\n                  - 0\n          columns:\n            - Single: invoice_no\n        \");\n\n        // oops, two arguments #339\n        let result = parse_resolve_and_lower(\n            \"\n        from c_invoice\n        aggregate average amount\n        \",\n        );\n        assert!(result.is_err());\n\n        // oops, two arguments\n        let result = parse_resolve_and_lower(\n            \"\n        from c_invoice\n        group issued_at (aggregate average amount)\n        \",\n        );\n        assert!(result.is_err());\n\n        // correct function call\n        let ctx = crate::semantic::test::parse_and_resolve(\n            \"\n        from c_invoice\n        group issued_at (\n            aggregate (average amount)\n        )\n        \",\n        )\n        .unwrap();\n        let (res, _) = ctx.find_main_rel(&[]).unwrap().clone();\n        let expr = res.clone().into_relation_var().unwrap();\n        let expr = super::super::test::erase_ids(*expr);\n        assert_yaml_snapshot!(expr);\n    }\n\n    #[test]\n    fn test_transform_sort() {\n        assert_yaml_snapshot!(parse_resolve_and_lower(\"\n        from invoices\n        sort {issued_at, -amount, +num_of_articles}\n        sort issued_at\n        sort (-issued_at)\n        sort {issued_at}\n        sort {-issued_at}\n        \").unwrap(), @r\"\n        def:\n          version: ~\n          other: {}\n        tables:\n          - id: 0\n            name: ~\n            relation:\n              kind:\n                ExternRef:\n                  LocalTable:\n                    - invoices\n              columns:\n                - Single: issued_at\n                - Single: amount\n                - Single: num_of_articles\n                - Wildcard\n        relation:\n          kind:\n            Pipeline:\n              - From:\n                  source: 0\n                  columns:\n                    - - Single: issued_at\n                      - 0\n                    - - Single: amount\n                      - 1\n                    - - Single: num_of_articles\n                      - 2\n                    - - Wildcard\n                      - 3\n                  name: invoices\n                  prefer_cte: true\n              - Sort:\n                  - direction: Asc\n                    column: 0\n                  - direction: Desc\n                    column: 1\n                  - direction: Asc\n                    column: 2\n              - Sort:\n                  - direction: Asc\n                    column: 0\n              - Sort:\n                  - direction: Desc\n                    column: 0\n              - Sort:\n                  - direction: Asc\n                    column: 0\n              - Sort:\n                  - direction: Desc\n                    column: 0\n              - Select:\n                  - 0\n                  - 1\n                  - 2\n                  - 3\n          columns:\n            - Single: issued_at\n            - Single: amount\n            - Single: num_of_articles\n            - Wildcard\n        \");\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/resolver/types.rs",
    "content": "use super::Resolver;\nuse crate::codegen::{write_ty, write_ty_kind};\nuse crate::ir::pl::*;\nuse crate::pr::{PrimitiveSet, Ty, TyKind, TyTupleField};\nuse crate::Result;\nuse crate::{Error, Reason, WithErrorInfo};\n\nimpl Resolver<'_> {\n    pub fn infer_type(expr: &Expr) -> Result<Option<Ty>> {\n        if let Some(ty) = &expr.ty {\n            return Ok(Some(ty.clone()));\n        }\n\n        let kind = match &expr.kind {\n            ExprKind::Literal(ref literal) => match literal {\n                Literal::Null => return Ok(None),\n                Literal::Integer(_) => TyKind::Primitive(PrimitiveSet::Int),\n                Literal::Float(_) => TyKind::Primitive(PrimitiveSet::Float),\n                Literal::Boolean(_) => TyKind::Primitive(PrimitiveSet::Bool),\n                Literal::String(_) => TyKind::Primitive(PrimitiveSet::Text),\n                Literal::RawString(_) => TyKind::Primitive(PrimitiveSet::Text),\n                Literal::Date(_) => TyKind::Primitive(PrimitiveSet::Date),\n                Literal::Time(_) => TyKind::Primitive(PrimitiveSet::Time),\n                Literal::Timestamp(_) => TyKind::Primitive(PrimitiveSet::Timestamp),\n                Literal::ValueAndUnit(_) => return Ok(None), // TODO\n            },\n\n            ExprKind::Ident(_) | ExprKind::FuncCall(_) => return Ok(None),\n\n            ExprKind::SString(_) => return Ok(None),\n            ExprKind::FString(_) => TyKind::Primitive(PrimitiveSet::Text),\n\n            ExprKind::TransformCall(_) => return Ok(None), // TODO\n            ExprKind::Tuple(fields) => {\n                let mut ty_fields: Vec<TyTupleField> = Vec::with_capacity(fields.len());\n                let has_other = false;\n\n                for field in fields {\n                    let ty = Resolver::infer_type(field)?;\n\n                    if field.flatten {\n                        if let Some(fields) = ty.as_ref().and_then(|x| x.kind.as_tuple()) {\n                            ty_fields.extend(fields.iter().cloned());\n                            continue;\n                        }\n                    }\n\n                    // TODO: move this into de-sugar stage (expand PL)\n                    // TODO: this will not infer nested namespaces\n                    let name = field\n                        .alias\n                        .clone()\n                        .or_else(|| field.kind.as_ident().map(|i| i.name.clone()));\n\n                    ty_fields.push(TyTupleField::Single(name, ty));\n                }\n\n                if has_other {\n                    ty_fields.push(TyTupleField::Wildcard(None));\n                }\n                ty_tuple_kind(ty_fields)\n            }\n            ExprKind::Array(items) => {\n                let mut item_tys = Vec::with_capacity(items.len());\n                for item in items {\n                    let item_ty = Resolver::infer_type(item)?;\n                    if let Some(item_ty) = item_ty {\n                        item_tys.push(item_ty);\n                    }\n                }\n                // TODO verify that types of all items are the same\n                let items_ty = item_tys.into_iter().next().map(Box::new);\n                TyKind::Array(items_ty)\n            }\n\n            _ => return Ok(None),\n        };\n        Ok(Some(Ty {\n            kind,\n            name: None,\n            span: None,\n        }))\n    }\n\n    /// Validates that found node has expected type. Returns assumed type of the node.\n    pub fn validate_expr_type<F>(\n        &mut self,\n        found: &mut Expr,\n        expected: Option<&Ty>,\n        who: &F,\n    ) -> Result<(), Error>\n    where\n        F: Fn() -> Option<String>,\n    {\n        if expected.is_none() {\n            // expected is none: there is no validation to be done\n            return Ok(());\n        };\n\n        let Some(found_ty) = &mut found.ty else {\n            // found is none: infer from expected\n\n            if found.lineage.is_none() && expected.unwrap().is_relation() {\n                // special case: infer a table type\n                // inferred tables are needed for s-strings that represent tables\n                // similarly as normal table references, we want to be able to infer columns\n                // of this table, which means it needs to be defined somewhere\n                // in the module structure.\n                let Some(id) = found.id else {\n                    // Expression has no id - this happens with bare lambdas like `x -> y`\n                    let found_desc = match &found.kind {\n                        ExprKind::Func(_) => \"a function\".to_string(),\n                        kind => format!(\"{kind:?}\"),\n                    };\n                    return Err(Error::new(Reason::Expected {\n                        who: None,\n                        expected: \"a table\".to_string(),\n                        found: found_desc,\n                    })\n                    .with_span(found.span));\n                };\n\n                let frame = self.declare_table_for_literal(id, None, found.alias.clone());\n\n                // override the empty frame with frame of the new table\n                found.lineage = Some(frame)\n            }\n\n            // base case: infer expected type\n            found.ty = expected.cloned();\n\n            return Ok(());\n        };\n\n        self.validate_type(found_ty, expected, who)\n            .with_span(found.span)\n    }\n\n    /// Validates that found node has expected type. Returns assumed type of the node.\n    pub fn validate_type<F>(\n        &mut self,\n        found: &mut Ty,\n        expected: Option<&Ty>,\n        who: &F,\n    ) -> Result<(), Error>\n    where\n        F: Fn() -> Option<String>,\n    {\n        // infer\n        let Some(expected) = expected else {\n            // expected is none: there is no validation to be done\n            return Ok(());\n        };\n\n        let expected_is_above = is_super_type_of(expected, found);\n        if expected_is_above {\n            return Ok(());\n        }\n\n        // A temporary hack for allowing calling window functions from within\n        // aggregate and derive.\n        if expected.kind.is_array() && !found.kind.is_function() {\n            return Ok(());\n        }\n\n        Err(compose_type_error(found, expected, who))\n    }\n}\n\npub fn ty_tuple_kind(fields: Vec<TyTupleField>) -> TyKind {\n    let mut res: Vec<TyTupleField> = Vec::with_capacity(fields.len());\n    for field in fields {\n        if let TyTupleField::Single(name, _) = &field {\n            // remove names from previous fields with the same name\n            if name.is_some() {\n                for f in res.iter_mut() {\n                    if f.as_single().and_then(|x| x.0.as_ref()) == name.as_ref() {\n                        *f.as_single_mut().unwrap().0 = None;\n                    }\n                }\n            }\n        }\n        res.push(field);\n    }\n    TyKind::Tuple(res)\n}\n\nfn compose_type_error<F>(found_ty: &mut Ty, expected: &Ty, who: &F) -> Error\nwhere\n    F: Fn() -> Option<String>,\n{\n    fn display_ty(ty: &Ty) -> String {\n        if ty.name.is_none() {\n            if let TyKind::Tuple(fields) = &ty.kind {\n                if fields.len() == 1 && fields[0].is_wildcard() {\n                    return \"a tuple\".to_string();\n                }\n            }\n        }\n        format!(\"type `{}`\", write_ty(ty))\n    }\n\n    let who = who();\n    let is_join = who\n        .as_ref()\n        .map(|x| x.contains(\"std.join\"))\n        .unwrap_or_default();\n\n    let mut e = Error::new(Reason::Expected {\n        who,\n        expected: display_ty(expected),\n        found: display_ty(found_ty),\n    });\n\n    if found_ty.kind.is_function() && !expected.kind.is_function() {\n        let found = found_ty.kind.as_function().unwrap();\n        let func_name = if let Some(func) = found {\n            func.name_hint.as_ref()\n        } else {\n            None\n        };\n        let to_what = func_name\n            .map(|n| format!(\"to function {n}\"))\n            .unwrap_or_else(|| \"in this function call?\".to_string());\n\n        e = e.push_hint(format!(\"Argument might be missing {to_what}?\"));\n    }\n\n    if is_join && found_ty.kind.is_tuple() && !expected.kind.is_tuple() {\n        e = e.push_hint(\"Try using `(...)` instead of `{...}`\");\n    }\n\n    if let Some(expected_name) = &expected.name {\n        let expanded = write_ty_kind(&expected.kind);\n        e = e.push_hint(format!(\"Type `{expected_name}` expands to `{expanded}`\"));\n    }\n    e\n}\n\n/// Analogous to [crate::ir::pl::Lineage::rename()]\npub fn rename_relation(ty_kind: &mut TyKind, alias: String) {\n    if let TyKind::Array(Some(items_ty)) = ty_kind {\n        rename_tuples(&mut items_ty.kind, alias);\n    }\n}\n\nfn rename_tuples(ty_kind: &mut TyKind, alias: String) {\n    flatten_tuples(ty_kind);\n\n    if let TyKind::Tuple(fields) = ty_kind {\n        let inner_fields = std::mem::take(fields);\n\n        let ty = Ty::new(TyKind::Tuple(inner_fields));\n        fields.push(TyTupleField::Single(Some(alias), Some(ty)));\n    }\n}\n\nfn flatten_tuples(ty_kind: &mut TyKind) {\n    if let TyKind::Tuple(fields) = ty_kind {\n        let mut new_fields = Vec::new();\n\n        for field in fields.drain(..) {\n            let TyTupleField::Single(name, Some(ty)) = field else {\n                new_fields.push(field);\n                continue;\n            };\n\n            // recurse\n            // let ty = ty.flatten_tuples();\n\n            let TyKind::Tuple(inner_fields) = ty.kind else {\n                new_fields.push(TyTupleField::Single(name, Some(ty)));\n                continue;\n            };\n            new_fields.extend(inner_fields);\n        }\n\n        fields.extend(new_fields);\n    }\n}\n\npub fn is_super_type_of(superset: &Ty, subset: &Ty) -> bool {\n    if superset.is_relation() && subset.is_relation() {\n        return true;\n    }\n    is_super_type_of_kind(&superset.kind, &subset.kind)\n}\n\npub fn is_super_type_of_opt(superset: Option<&Ty>, subset: Option<&Ty>) -> bool {\n    let Some(subset) = subset else {\n        return true;\n    };\n    let Some(superset) = superset else {\n        return true;\n    };\n    is_super_type_of_kind(&superset.kind, &subset.kind)\n}\n\npub fn is_sub_type_of_array(ty: &Ty) -> bool {\n    let array = TyKind::Array(None);\n    is_super_type_of_kind(&array, &ty.kind)\n}\n\nfn is_super_type_of_kind(superset: &TyKind, subset: &TyKind) -> bool {\n    match (superset, subset) {\n        (TyKind::Primitive(l0), TyKind::Primitive(r0)) => l0 == r0,\n\n        (TyKind::Function(None), TyKind::Function(_)) => true,\n        (TyKind::Function(Some(_)), TyKind::Function(None)) => true,\n        (TyKind::Function(Some(sup)), TyKind::Function(Some(sub))) => {\n            if is_not_super_type_of(sup.return_ty.as_deref(), sub.return_ty.as_deref()) {\n                return false;\n            }\n            if sup.params.len() != sub.params.len() {\n                return false;\n            }\n            for (sup_arg, sub_arg) in sup.params.iter().zip(&sub.params) {\n                if is_not_super_type_of(sup_arg.as_ref(), sub_arg.as_ref()) {\n                    return false;\n                }\n            }\n\n            true\n        }\n\n        (TyKind::Array(sup), TyKind::Array(sub)) => {\n            is_super_type_of_opt(sup.as_deref(), sub.as_deref())\n        }\n\n        (TyKind::Tuple(sup_tuple), TyKind::Tuple(sub_tuple)) => {\n            let sup_has_wildcard = sup_tuple\n                .iter()\n                .any(|f| matches!(f, TyTupleField::Wildcard(_)));\n            let sub_has_wildcard = sub_tuple\n                .iter()\n                .any(|f| matches!(f, TyTupleField::Wildcard(_)));\n\n            let mut sup_fields = sup_tuple.iter().filter(|f| f.is_single());\n            let mut sub_fields = sub_tuple.iter().filter(|f| f.is_single());\n\n            loop {\n                let sup = sup_fields.next();\n                let sub = sub_fields.next();\n\n                match (sup, sub) {\n                    (Some(TyTupleField::Single(_, sup)), Some(TyTupleField::Single(_, sub))) => {\n                        if is_not_super_type_of(sup.as_ref(), sub.as_ref()) {\n                            return false;\n                        }\n                    }\n                    (_, Some(_)) => {\n                        if !sup_has_wildcard {\n                            return false;\n                        }\n                    }\n                    (Some(_), None) => {\n                        if !sub_has_wildcard {\n                            return false;\n                        }\n                    }\n                    (None, None) => break,\n                }\n            }\n            true\n        }\n\n        (l, r) => l == r,\n    }\n}\n\nfn is_not_super_type_of(sup: Option<&Ty>, sub: Option<&Ty>) -> bool {\n    if let Some(sub_ret) = sub {\n        if let Some(sup_ret) = sup {\n            if !is_super_type_of(sup_ret, sub_ret) {\n                return true;\n            }\n        }\n    }\n    false\n}\n\nfn maybe_type_intersection(a: Option<Ty>, b: Option<Ty>) -> Option<Ty> {\n    match (a, b) {\n        (Some(a), Some(b)) => Some(type_intersection(a, b)),\n        (x, None) | (None, x) => x,\n    }\n}\n\npub fn type_intersection(a: Ty, b: Ty) -> Ty {\n    match (a.kind, b.kind) {\n        (a_kind, b_kind) if a_kind == b_kind => Ty { kind: a_kind, ..a },\n\n        // tuple\n        (TyKind::Tuple(a_fields), TyKind::Tuple(b_fields)) => {\n            type_intersection_of_tuples(a_fields, b_fields)\n        }\n\n        // array\n        (TyKind::Array(Some(a)), TyKind::Array(Some(b))) => {\n            Ty::new(TyKind::Array(Some(Box::new(type_intersection(*a, *b)))))\n        }\n\n        _ => todo!(),\n    }\n}\n\nfn type_intersection_of_tuples(a: Vec<TyTupleField>, b: Vec<TyTupleField>) -> Ty {\n    let a_has_other = a.iter().any(|f| f.is_wildcard());\n    let b_has_other = b.iter().any(|f| f.is_wildcard());\n\n    let mut a_fields = a.into_iter().filter_map(|f| f.into_single().ok());\n    let mut b_fields = b.into_iter().filter_map(|f| f.into_single().ok());\n\n    let mut fields = Vec::new();\n    let mut has_other = false;\n    loop {\n        match (a_fields.next(), b_fields.next()) {\n            (None, None) => break,\n            (None, Some(b_field)) => {\n                if !a_has_other {\n                    todo!();\n                }\n                has_other = true;\n                fields.push(TyTupleField::Single(b_field.0, b_field.1));\n            }\n            (Some(a_field), None) => {\n                if !b_has_other {\n                    todo!();\n                }\n                has_other = true;\n                fields.push(TyTupleField::Single(a_field.0, a_field.1));\n            }\n            (Some((a_name, a_ty)), Some((b_name, b_ty))) => {\n                let name = match (a_name, b_name) {\n                    (Some(a), Some(b)) if a == b => Some(a),\n                    (None, None) | (Some(_), Some(_)) => None,\n                    (None, Some(n)) | (Some(n), None) => Some(n),\n                };\n                let ty = maybe_type_intersection(a_ty, b_ty);\n\n                fields.push(TyTupleField::Single(name, ty));\n            }\n        }\n    }\n    if has_other {\n        fields.push(TyTupleField::Wildcard(None));\n    }\n\n    Ty::new(TyKind::Tuple(fields))\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/semantic/std.prql",
    "content": "# The PRQL standard library defines the following functions and transforms.\n# The definitions are whitespace insensitive, and have this form:\n#\n# ```\n# let my_func = param1 param2 ...  -> <return_type> body_expr\n# ```\n#\n# Where:\n#   * `my_func` is the name of the function\n#   * `param1` is the first parameter optionally followed by a type in \"< ... >\"\n#   * `param2` etc. follow the same pattern as param1\n#   * `<return_type>` is the type of result wrapped in \"< ... >\"\n#   * `body_expr` defines the function body that creates the result.\n#     It can be PRQL code or `internal ...` to indicate internal compiler code.\n\n# Operators\n\nlet mul = left right -> internal std.mul\nlet div_i = left right -> internal std.div_i\nlet div_f = left right -> internal std.div_f\nlet mod = left right -> internal std.mod\nlet add = left right -> internal std.add\nlet sub = left right -> internal std.sub\nlet eq = left right -> <bool> internal std.eq\nlet ne = left right -> <bool> internal std.ne\nlet gt = left right -> <bool> internal std.gt\nlet lt = left right -> <bool> internal std.lt\nlet gte = left right -> <bool> internal std.gte\nlet lte = left right -> <bool> internal std.lte\nlet and = left<bool> right<bool> -> <bool> internal std.and\nlet or = left<bool> right<bool> -> <bool> internal std.or\nlet coalesce = left right -> internal std.coalesce\nlet regex_search = text pattern -> <bool> internal std.regex_search\n\nlet neg = expr -> internal std.neg\nlet not = expr<bool> -> <bool> internal std.not\n\n# Types\n\n## Type primitives\ntype int = int\ntype float = float\ntype bool = bool\ntype text = text\ntype date = date\ntype time = time\ntype timestamp = timestamp\ntype `func` = func\n\n## Generic array\ntype array = []\n\n## Generic relation\ntype relation = [{..}]\n\n## Range\ntype range = {start = *, end = *}\n\n## Transform\ntype transform = func relation -> relation\n\n# Functions\n\n## Relational transforms\nlet from = func\n  `default_db.source` <relation>\n  -> <relation> source\n\nlet select = func\n  columns\n  tbl <relation>\n  -> <relation> internal select\n\nlet filter = func\n  condition <bool>\n  tbl <relation>\n  -> <relation> internal filter\n\nlet derive = func\n  columns\n  tbl <relation>\n  -> <relation> internal derive\n\nlet aggregate = func\n  columns\n  tbl <relation>\n  -> <relation> internal aggregate\n\nlet sort = func\n  by\n  tbl <relation>\n  -> <relation> internal sort\n\nlet take = func\n  expr\n  tbl <relation>\n  -> <relation> internal take\n\nlet join = func\n  `default_db.with` <relation>\n  condition <bool>\n  `noresolve.side`:inner\n  tbl <relation>\n  -> <relation> internal join\n\nlet group = func\n  by\n  pipeline <transform>\n  tbl <relation>\n  -> <relation> internal group\n\nlet window = func\n  rows:0..-1\n  range:0..-1\n  expanding <bool>:false\n  rolling <int>:0\n  pipeline <transform>\n  tbl <relation>\n  -> <relation> internal window\n\nlet append = `default_db.bottom`<relation> top<relation> -> <relation> internal append\nlet intersect = `default_db.bottom`<relation> top<relation> -> <relation> (\n  t = top\n  join (b = bottom) (tuple_every (tuple_map _eq (tuple_zip t.* b.*)))\n  select t.*\n)\nlet remove = `default_db.bottom`<relation> top<relation> -> <relation> (\n  t = top\n  join side:left (b = bottom) (tuple_every (tuple_map _eq (tuple_zip t.* b.*)))\n  filter (tuple_every (tuple_map _is_null b.*))\n  select t.*\n)\nlet loop = func\n  pipeline <transform>\n  top <relation>\n  -> <relation> internal loop\n\n## Aggregate functions\n# These return either a scalar when used within `aggregate`, or a column when used anywhere else.\n\nlet min = column <array> -> internal std.min\n\nlet max = column <array> -> internal std.max\n\nlet sum = column <array> -> internal std.sum\n\nlet average = column <array> -> internal std.average\n\nlet stddev = column <array> -> internal std.stddev\n\nlet all = column <array> -> <bool> internal std.all\n\nlet any = column <array> -> <bool> internal std.any\n\nlet concat_array = column <array> -> <text> internal std.concat_array\n\n# Counts number of items in the column.\n# Note that the count will include null values.\nlet count = column <array> -> <int> internal count\n\n# Deprecated in favour of filtering input to the [std.count] function (not yet implemented).\n@{deprecated}\nlet count_distinct = column <array> -> internal std.count_distinct\n\n## Window functions\nlet lag =   offset <int>    column <array> -> internal std.lag\nlet lead =  offset <int>    column <array> -> internal std.lead\nlet first      = column <array> -> internal std.first\nlet last       = column <array> -> internal std.last\nlet rank       = column <array> -> internal std.rank\nlet rank_dense = column <array> -> internal std.rank_dense\nlet row_number = column <array> -> internal row_number\n\n# Mathematical functions\nmodule math {\n  let abs = column -> internal std.math.abs\n  let floor = column -> <int> internal std.math.floor\n  let ceil = column -> <int> internal std.math.ceil\n  let pi = -> <float> internal std.math.pi\n  let exp = column -> internal std.math.exp\n  let ln = column -> internal std.math.ln\n  let log10 = column -> internal std.math.log10\n  let log = func base column -> internal std.math.log\n  let sqrt = column -> internal std.math.sqrt\n  let degrees = column -> internal std.math.degrees\n  let radians = column -> internal std.math.radians\n  let cos = column -> internal std.math.cos\n  let acos = column -> internal std.math.acos\n  let sin = column -> internal std.math.sin\n  let asin = column -> internal std.math.asin\n  let tan = column -> internal std.math.tan\n  let atan = column -> internal std.math.atan\n  let pow = exponent column -> internal std.math.pow\n  let round = n_digits column -> internal std.math.round\n}\n\n## Misc functions\nlet as = `noresolve.type` column -> internal std.as\nlet in = pattern value -> <bool> internal in\n\n## Tuple functions\nlet tuple_every = func list -> <bool> internal tuple_every\nlet tuple_map = func fn <func> list -> internal tuple_map\nlet tuple_zip = func a b -> internal tuple_zip\nlet _eq = func a -> internal _eq\nlet _is_null = func a -> _param.a == null\n\n## Misc\nlet from_text = input<text> `noresolve.format`:csv -> <relation> internal from_text\n\n## Text functions\nmodule text {\n  let lower = column -> <text> internal std.text.lower\n  let upper = column -> <text> internal std.text.upper\n  let ltrim = column -> <text> internal std.text.ltrim\n  let rtrim = column -> <text> internal std.text.rtrim\n  let trim = column -> <text> internal std.text.trim\n  let length = column -> <int> internal std.text.length\n  let extract = offset<int> length<int> column -> <text> internal std.text.extract\n  let replace = pattern<text> replacement<text> column -> <text> internal std.text.replace\n  let starts_with = prefix<text> column -> <bool> internal std.text.starts_with\n  let contains = substr<text> column -> <bool> internal std.text.contains\n  let ends_with = suffix<text> column -> <bool> internal std.text.ends_with\n}\n\n## Date functions\nmodule date {\n  let to_text = format<text> column -> <text> internal std.date.to_text\n}\n\n## File-reading functions, primarily for DuckDB\nlet read_parquet = func\n  source <text>\n  binary_as_string <bool>:false\n  file_row_number <bool>:false\n  hive_partitioning <bool>:null\n  union_by_name <bool>:false\n  -> <relation> internal std.read_parquet\nlet read_csv = source<text> -> <relation> internal std.read_csv\nlet read_json = source<text> -> <relation> internal std.read_json\n\n\n## PRQL compiler functions\nmodule `prql` {\n  let version = -> <text> internal prql_version\n}\n\n# Deprecated, will be removed in 0.12.0\nlet prql_version = -> <text> internal prql_version\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/dialect.rs",
    "content": "//! Feature map for SQL dialects.\n//!\n//! The general principle with is to strive to target only the generic (i.e. default) dialect.\n//!\n//! This means that we prioritize common dialects and old dialect versions, because such\n//! implementations would also be supported by newer versions.\n//!\n//! Dialect-specifics should be added only if:\n//! - the generic dialect is not supported (i.e. LIMIT is not supported in MS SQL),\n//! - dialect-specific impl is more performant than generic impl.\n//!\n//! As a consequence, generated SQL may be verbose, since it will avoid newer or less adopted SQL\n//! constructs. The upside is much less complex translator.\nuse core::fmt::Debug;\nuse std::any::{Any, TypeId};\n\nuse chrono::format::{Fixed, Item, Numeric, Pad, StrftimeItems};\nuse serde::{Deserialize, Serialize};\nuse sqlparser::ast::DateTimeField;\nuse strum::VariantNames;\n\nuse crate::{Error, Result};\n\n/// Convert a chrono format `Item` back to its strftime string representation.\nfn chrono_item_to_strftime(item: &Item) -> String {\n    let pad_char = |pad: &Pad| match pad {\n        Pad::None => \"-\",\n        Pad::Zero => \"\",\n        Pad::Space => \"_\",\n    };\n    let numeric_char = |num: &Numeric| -> String {\n        match num {\n            Numeric::Year => \"Y\",\n            Numeric::YearMod100 => \"y\",\n            Numeric::Month => \"m\",\n            Numeric::Day => \"d\",\n            Numeric::Hour => \"H\",\n            Numeric::Hour12 => \"I\",\n            Numeric::Minute => \"M\",\n            Numeric::Second => \"S\",\n            Numeric::Nanosecond => \"f\",\n            _ => return format!(\"{num:?}\"),\n        }\n        .to_string()\n    };\n    match item {\n        Item::Numeric(num, pad) => format!(\"%{}{}\", pad_char(pad), numeric_char(num)),\n        Item::Fixed(Fixed::ShortMonthName) => \"%b\".to_string(),\n        Item::Fixed(Fixed::LongMonthName) => \"%B\".to_string(),\n        Item::Fixed(Fixed::ShortWeekdayName) => \"%a\".to_string(),\n        Item::Fixed(Fixed::LongWeekdayName) => \"%A\".to_string(),\n        Item::Fixed(Fixed::UpperAmPm) => \"%p\".to_string(),\n        Item::Fixed(Fixed::LowerAmPm) => \"%P\".to_string(),\n        Item::Fixed(Fixed::RFC3339) => \"%+\".to_string(),\n        _ => format!(\"{item:?}\"),\n    }\n}\n\n/// SQL dialect.\n///\n/// This only changes the output for a relatively small subset of features.\n///\n/// If something does not work in a specific dialect, please raise in a\n/// GitHub issue.\n// Make sure to update Python bindings, JS bindings & docs in the book.\n#[derive(\n    Debug,\n    PartialEq,\n    Eq,\n    Clone,\n    Copy,\n    Serialize,\n    Default,\n    Deserialize,\n    strum::Display,\n    strum::EnumIter,\n    strum::EnumMessage,\n    strum::EnumString,\n    strum::VariantNames,\n)]\n#[strum(serialize_all = \"lowercase\")]\npub enum Dialect {\n    Ansi,\n    BigQuery,\n    ClickHouse,\n    DuckDb,\n    #[default]\n    Generic,\n    GlareDb,\n    MsSql,\n    MySql,\n    Postgres,\n    Redshift,\n    SQLite,\n    Snowflake,\n}\n\n// Is this the best approach for the Enum / Struct — basically that we have one\n// Enum that gets its respective Struct, and then the Struct can also get its\n// respective Enum?\n\nimpl Dialect {\n    pub(super) fn handler(&self) -> Box<dyn DialectHandler> {\n        match self {\n            Dialect::MsSql => Box::new(MsSqlDialect),\n            Dialect::MySql => Box::new(MySqlDialect),\n            Dialect::BigQuery => Box::new(BigQueryDialect),\n            Dialect::SQLite => Box::new(SQLiteDialect),\n            Dialect::ClickHouse => Box::new(ClickHouseDialect),\n            Dialect::Snowflake => Box::new(SnowflakeDialect),\n            Dialect::DuckDb => Box::new(DuckDbDialect),\n            Dialect::Postgres => Box::new(PostgresDialect),\n            Dialect::Redshift => Box::new(RedshiftDialect),\n            Dialect::GlareDb => Box::new(GlareDbDialect),\n            Dialect::Ansi | Dialect::Generic => Box::new(GenericDialect),\n        }\n    }\n\n    pub fn support_level(&self) -> SupportLevel {\n        match self {\n            Dialect::DuckDb\n            | Dialect::SQLite\n            | Dialect::Postgres\n            | Dialect::Redshift\n            | Dialect::MySql\n            | Dialect::Generic\n            | Dialect::GlareDb\n            | Dialect::ClickHouse => SupportLevel::Supported,\n            Dialect::MsSql | Dialect::Ansi | Dialect::BigQuery | Dialect::Snowflake => {\n                SupportLevel::Unsupported\n            }\n        }\n    }\n\n    #[deprecated(note = \"Use `Dialect::VARIANTS` instead\")]\n    pub fn names() -> &'static [&'static str] {\n        Dialect::VARIANTS\n    }\n}\n\npub enum SupportLevel {\n    Supported,\n    Unsupported,\n    Nascent,\n}\n\n#[derive(Debug)]\npub struct GenericDialect;\n#[derive(Debug)]\npub struct SQLiteDialect;\n#[derive(Debug)]\npub struct MySqlDialect;\n#[derive(Debug)]\npub struct MsSqlDialect;\n#[derive(Debug)]\npub struct BigQueryDialect;\n#[derive(Debug)]\npub struct ClickHouseDialect;\n#[derive(Debug)]\npub struct SnowflakeDialect;\n#[derive(Debug)]\npub struct DuckDbDialect;\n#[derive(Debug)]\npub struct PostgresDialect;\n#[derive(Debug)]\npub struct RedshiftDialect;\n#[derive(Debug)]\npub struct GlareDbDialect;\n\npub(super) enum ColumnExclude {\n    Exclude,\n    Except,\n}\n\npub enum IdentQuotingStyle {\n    AlwaysQuoted,\n    ConditionallyQuoted,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum IntervalQuotingStyle {\n    // INTERVAL 1 day\n    NoQuotes,\n    // INTERVAL '1' day\n    ValueQuoted,\n    // INTERVAL '1 day'\n    ValueAndUnitQuoted,\n}\n\npub(super) trait DialectHandler: Any + Debug {\n    fn use_fetch(&self) -> bool {\n        false\n    }\n\n    fn ident_quote(&self) -> char {\n        '\"'\n    }\n\n    fn ident_quoting_style(&self) -> IdentQuotingStyle {\n        IdentQuotingStyle::ConditionallyQuoted\n    }\n\n    fn column_exclude(&self) -> Option<ColumnExclude> {\n        None\n    }\n\n    /// Support for DISTINCT in set ops (UNION DISTINCT, INTERSECT DISTINCT)\n    /// When not supported we fallback to implicit DISTINCT.\n    fn set_ops_distinct(&self) -> bool {\n        true\n    }\n\n    /// Support or EXCEPT ALL.\n    /// When not supported, fallback to anti join.\n    fn except_all(&self) -> bool {\n        true\n    }\n\n    fn intersect_all(&self) -> bool {\n        self.except_all()\n    }\n\n    /// Support for CONCAT function.\n    /// When not supported we fallback to use `||` as concat operator.\n    fn has_concat_function(&self) -> bool {\n        true\n    }\n\n    /// Whether or not intervals such as `INTERVAL 1 HOUR` require quotes like\n    /// `INTERVAL '1 HOUR'` or `INTERVAL '1' HOUR`\n    fn interval_quoting_style(&self, _dtf: &DateTimeField) -> IntervalQuotingStyle {\n        IntervalQuotingStyle::NoQuotes\n    }\n\n    /// Support for GROUP BY *\n    fn stars_in_group(&self) -> bool {\n        true\n    }\n\n    fn supports_distinct_on(&self) -> bool {\n        false\n    }\n\n    /// Get the date format for the given dialect\n    /// PRQL uses the same format as `chrono` crate\n    /// (see https://docs.rs/chrono/latest/chrono/format/strftime/index.html)\n    fn translate_prql_date_format(&self, prql_date_format: &str) -> Result<String> {\n        Ok(StrftimeItems::new(prql_date_format)\n            .map(|item| self.translate_chrono_item(item))\n            .collect::<Result<Vec<_>>>()?\n            .join(\"\"))\n    }\n\n    fn translate_chrono_item(&self, _item: Item) -> Result<String> {\n        Err(Error::new_simple(\n            \"Date formatting is not yet supported for this dialect\",\n        ))\n    }\n\n    fn supports_zero_columns(&self) -> bool {\n        false\n    }\n\n    fn translate_sql_array(\n        &self,\n        elements: Vec<sqlparser::ast::Expr>,\n    ) -> crate::Result<sqlparser::ast::Expr> {\n        use sqlparser::ast::Expr;\n\n        // Default SQL syntax: [elem1, elem2, ...]\n        Ok(Expr::Array(sqlparser::ast::Array {\n            elem: elements,\n            named: false,\n        }))\n    }\n\n    /// Whether source and subqueries should be put between simple parentheses\n    /// for `UNION` and similar verbs.\n    fn prefers_subquery_parentheses_shorthand(&self) -> bool {\n        false\n    }\n\n    /// Whether window functions require an ORDER BY clause.\n    /// Snowflake requires ORDER BY for ranking functions like ROW_NUMBER().\n    fn requires_order_by_in_window_function(&self) -> bool {\n        false\n    }\n}\n\nimpl dyn DialectHandler {\n    #[inline]\n    pub fn is<T: DialectHandler + 'static>(&self) -> bool {\n        TypeId::of::<T>() == self.type_id()\n    }\n}\n\nimpl DialectHandler for GenericDialect {\n    fn translate_chrono_item(&self, _item: Item) -> Result<String> {\n        Err(Error::new_simple(\"Date formatting requires a dialect\"))\n    }\n}\n\nimpl DialectHandler for PostgresDialect {\n    fn interval_quoting_style(&self, _dtf: &DateTimeField) -> IntervalQuotingStyle {\n        IntervalQuotingStyle::ValueAndUnitQuoted\n    }\n\n    fn supports_distinct_on(&self) -> bool {\n        true\n    }\n\n    // https://www.postgresql.org/docs/current/functions-formatting.html\n    fn translate_chrono_item<'a>(&self, item: Item) -> Result<String> {\n        Ok(match item {\n            Item::Numeric(Numeric::Year, Pad::Zero) => \"YYYY\".to_string(),\n            Item::Numeric(Numeric::YearMod100, Pad::Zero) => \"YY\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::None) => \"FMMM\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::Zero) => \"MM\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::None) => \"FMDD\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::Zero) => \"DD\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::None) => \"FMHH24\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::Zero) => \"HH24\".to_string(),\n            Item::Numeric(Numeric::Hour12, Pad::Zero) => \"HH12\".to_string(),\n            Item::Numeric(Numeric::Minute, Pad::Zero) => \"MI\".to_string(),\n            Item::Numeric(Numeric::Second, Pad::Zero) => \"SS\".to_string(),\n            Item::Numeric(Numeric::Nanosecond, Pad::Zero) => \"US\".to_string(), // Microseconds\n            Item::Fixed(Fixed::ShortMonthName) => \"Mon\".to_string(),\n            // By default long names are blank-padded to 9 chars so we need to use FM prefix\n            Item::Fixed(Fixed::LongMonthName) => \"FMMonth\".to_string(),\n            Item::Fixed(Fixed::ShortWeekdayName) => \"Dy\".to_string(),\n            Item::Fixed(Fixed::LongWeekdayName) => \"FMDay\".to_string(),\n            Item::Fixed(Fixed::UpperAmPm) => \"AM\".to_string(),\n            Item::Fixed(Fixed::RFC3339) => \"YYYY-MM-DD\\\"T\\\"HH24:MI:SS.USZ\".to_string(),\n            Item::Literal(literal) => {\n                // literals are split at every non alphanumeric character\n                if literal.chars().any(|c| c.is_ascii_alphanumeric()) {\n                    // If the literal contains alphanumeric characters, we need to quote it\n                    // to avoid it being interpreted as a pattern understood by Postgres.\n                    // We hence need to put it in double quotes to force it to be interpreted as literal text\n                    format!(\"\\\"{literal}\\\"\")\n                } else {\n                    literal.replace('\\'', \"''\").replace('\"', \"\\\\\\\"\")\n                }\n            }\n            Item::Space(spaces) => spaces.to_string(),\n            _ => {\n                return Err(Error::new_simple(\n                    \"PRQL doesn't support this format specifier\",\n                ))\n            }\n        })\n    }\n\n    fn supports_zero_columns(&self) -> bool {\n        true\n    }\n\n    fn prefers_subquery_parentheses_shorthand(&self) -> bool {\n        true\n    }\n}\n\nimpl DialectHandler for RedshiftDialect {\n    fn ident_quoting_style(&self) -> IdentQuotingStyle {\n        // Use conditional quoting with dialect-specific keywords\n        IdentQuotingStyle::ConditionallyQuoted\n    }\n\n    fn interval_quoting_style(&self, dtf: &DateTimeField) -> IntervalQuotingStyle {\n        if matches!(dtf, DateTimeField::Week(_) | DateTimeField::Weeks) {\n            IntervalQuotingStyle::ValueAndUnitQuoted\n        } else {\n            IntervalQuotingStyle::ValueQuoted\n        }\n    }\n\n    fn supports_distinct_on(&self) -> bool {\n        false\n    }\n\n    // https://docs.aws.amazon.com/redshift/latest/dg/r_FORMAT_strings.html\n    fn translate_chrono_item<'a>(&self, item: Item) -> Result<String> {\n        Ok(match item {\n            Item::Numeric(Numeric::Year, Pad::Zero) => \"YYYY\".to_string(),\n            Item::Numeric(Numeric::YearMod100, Pad::Zero) => \"YY\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::None) => \"FMMM\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::Zero) => \"MM\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::None) => \"FMDD\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::Zero) => \"DD\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::None) => \"FMHH24\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::Zero) => \"HH24\".to_string(),\n            Item::Numeric(Numeric::Hour12, Pad::Zero) => \"HH12\".to_string(),\n            Item::Numeric(Numeric::Minute, Pad::Zero) => \"MI\".to_string(),\n            Item::Numeric(Numeric::Second, Pad::Zero) => \"SS\".to_string(),\n            Item::Numeric(Numeric::Nanosecond, Pad::Zero) => \"US\".to_string(), // Microseconds\n            Item::Fixed(Fixed::ShortMonthName) => \"Mon\".to_string(),\n            // By default long names are blank-padded to 9 chars so we need to use FM prefix\n            Item::Fixed(Fixed::LongMonthName) => \"FMMonth\".to_string(),\n            Item::Fixed(Fixed::ShortWeekdayName) => \"Dy\".to_string(),\n            Item::Fixed(Fixed::LongWeekdayName) => \"FMDay\".to_string(),\n            Item::Fixed(Fixed::UpperAmPm) => \"AM\".to_string(),\n            Item::Fixed(Fixed::RFC3339) => \"YYYY-MM-DD\\\"T\\\"HH24:MI:SS.USZ\".to_string(),\n            Item::Literal(literal) => {\n                // literals are split at every non alphanumeric character\n                if literal.chars().any(|c| c.is_ascii_alphanumeric()) {\n                    // If the literal contains alphanumeric characters, we need to quote it\n                    // to avoid it being interpreted as a pattern understood by Redshift.\n                    // We hence need to put it in double quotes to force it to be interpreted as literal text\n                    format!(\"\\\"{literal}\\\"\")\n                } else {\n                    literal.replace('\\'', \"''\").replace('\"', \"\\\\\\\"\")\n                }\n            }\n            Item::Space(spaces) => spaces.to_string(),\n            _ => {\n                return Err(Error::new_simple(\n                    \"PRQL doesn't support this format specifier\",\n                ))\n            }\n        })\n    }\n\n    fn supports_zero_columns(&self) -> bool {\n        true\n    }\n\n    fn prefers_subquery_parentheses_shorthand(&self) -> bool {\n        true\n    }\n\n    // Redshift only supports CONCAT with two elements, the docs recommend to use the || operator\n    // for more than two elements: https://docs.aws.amazon.com/redshift/latest/dg/r_CONCAT.html\n    fn has_concat_function(&self) -> bool {\n        false\n    }\n}\n\nimpl DialectHandler for GlareDbDialect {\n    fn interval_quoting_style(&self, _dtf: &DateTimeField) -> IntervalQuotingStyle {\n        IntervalQuotingStyle::ValueAndUnitQuoted\n    }\n}\n\nimpl DialectHandler for SQLiteDialect {\n    fn set_ops_distinct(&self) -> bool {\n        false\n    }\n\n    fn except_all(&self) -> bool {\n        false\n    }\n\n    fn has_concat_function(&self) -> bool {\n        false\n    }\n\n    fn stars_in_group(&self) -> bool {\n        false\n    }\n}\n\nimpl DialectHandler for MsSqlDialect {\n    fn use_fetch(&self) -> bool {\n        true\n    }\n\n    // https://learn.microsoft.com/en-us/sql/t-sql/language-elements/set-operators-except-and-intersect-transact-sql?view=sql-server-ver16\n    fn except_all(&self) -> bool {\n        false\n    }\n\n    fn set_ops_distinct(&self) -> bool {\n        false\n    }\n\n    // https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings\n    fn translate_chrono_item<'a>(&self, item: Item) -> Result<String> {\n        Ok(match item {\n            Item::Numeric(Numeric::Year, Pad::Zero) => \"yyyy\".to_string(),\n            Item::Numeric(Numeric::YearMod100, Pad::Zero) => \"yy\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::None) => \"M\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::Zero) => \"MM\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::None) => \"d\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::Zero) => \"dd\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::None) => \"H\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::Zero) => \"HH\".to_string(),\n            Item::Numeric(Numeric::Hour12, Pad::Zero) => \"hh\".to_string(),\n            Item::Numeric(Numeric::Minute, Pad::Zero) => \"mm\".to_string(),\n            Item::Numeric(Numeric::Second, Pad::Zero) => \"ss\".to_string(),\n            Item::Numeric(Numeric::Nanosecond, Pad::Zero) => \"ffffff\".to_string(), // Microseconds\n            Item::Fixed(Fixed::ShortMonthName) => \"MMM\".to_string(),\n            Item::Fixed(Fixed::LongMonthName) => \"MMMM\".to_string(),\n            Item::Fixed(Fixed::ShortWeekdayName) => \"ddd\".to_string(),\n            Item::Fixed(Fixed::LongWeekdayName) => \"dddd\".to_string(),\n            Item::Fixed(Fixed::UpperAmPm) => \"tt\".to_string(),\n            Item::Fixed(Fixed::RFC3339) => \"yyyy-MM-dd'T'HH:mm:ss.ffffff'Z'\".to_string(),\n            Item::Literal(literal) => {\n                // literals are split at every non alphanumeric character\n                if literal.chars().any(|c| c.is_ascii_alphanumeric()) {\n                    // If the literal contains alphanumeric characters, we need to quote it\n                    // to avoid it being interpreted as a pattern understood by MSSQL.\n                    // We hence need to put it in double quotes to force it to be interpreted as literal text\n                    format!(\"\\\"{literal}\\\"\")\n                } else {\n                    // MSSQL uses single quotes around\n                    literal\n                        .replace('\"', \"\\\\\\\"\")\n                        .replace('\\'', \"\\\"\\'\\\"\")\n                        .replace('%', \"\\\\%\")\n                }\n            }\n            Item::Space(spaces) => spaces.to_string(),\n            _ => {\n                return Err(Error::new_simple(\n                    \"PRQL doesn't support this format specifier\",\n                ))\n            }\n        })\n    }\n}\n\nimpl DialectHandler for MySqlDialect {\n    fn ident_quote(&self) -> char {\n        '`'\n    }\n\n    fn set_ops_distinct(&self) -> bool {\n        // https://dev.mysql.com/doc/refman/8.0/en/set-operations.html\n        true\n    }\n\n    // https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format\n    fn translate_chrono_item<'a>(&self, item: Item) -> Result<String> {\n        Ok(match item {\n            Item::Numeric(Numeric::Year, Pad::Zero) => \"%Y\".to_string(),\n            Item::Numeric(Numeric::YearMod100, Pad::Zero) => \"%y\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::None) => \"%c\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::Zero) => \"%m\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::None) => \"%e\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::Zero) => \"%d\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::None) => \"%k\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::Zero) => \"%H\".to_string(),\n            Item::Numeric(Numeric::Hour12, Pad::Zero) => \"%I\".to_string(),\n            Item::Numeric(Numeric::Minute, Pad::Zero) => \"%i\".to_string(),\n            Item::Numeric(Numeric::Second, Pad::Zero) => \"%S\".to_string(),\n            Item::Numeric(Numeric::Nanosecond, Pad::Zero) => \"%f\".to_string(), // Microseconds\n            Item::Fixed(Fixed::ShortMonthName) => \"%b\".to_string(),\n            Item::Fixed(Fixed::LongMonthName) => \"%M\".to_string(),\n            Item::Fixed(Fixed::ShortWeekdayName) => \"%a\".to_string(),\n            Item::Fixed(Fixed::LongWeekdayName) => \"%W\".to_string(),\n            Item::Fixed(Fixed::UpperAmPm) => \"%p\".to_string(),\n            Item::Fixed(Fixed::RFC3339) => \"%Y-%m-%dT%H:%i:%S.%fZ\".to_string(),\n            Item::Literal(literal) => literal.replace('\\'', \"''\").replace('%', \"%%\"),\n            Item::Space(spaces) => spaces.to_string(),\n            _ => {\n                return Err(Error::new_simple(\n                    \"PRQL doesn't support this format specifier\",\n                ))\n            }\n        })\n    }\n}\n\nimpl DialectHandler for ClickHouseDialect {\n    fn ident_quote(&self) -> char {\n        '`'\n    }\n\n    fn supports_distinct_on(&self) -> bool {\n        true\n    }\n\n    // https://clickhouse.com/docs/en/sql-reference/functions/date-time-functions#formatDateTimeInJodaSyntax\n    fn translate_chrono_item<'a>(&self, item: Item) -> Result<String> {\n        Ok(match item {\n            Item::Numeric(Numeric::Year, Pad::Zero) => \"yyyy\".to_string(),\n            Item::Numeric(Numeric::YearMod100, Pad::Zero) => \"yy\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::None) => \"M\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::Zero) => \"MM\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::None) => \"d\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::Zero) => \"dd\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::None) => \"H\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::Zero) => \"HH\".to_string(),\n            Item::Numeric(Numeric::Hour12, Pad::Zero) => \"hh\".to_string(),\n            Item::Numeric(Numeric::Minute, Pad::Zero) => \"mm\".to_string(),\n            Item::Numeric(Numeric::Second, Pad::Zero) => \"ss\".to_string(),\n            Item::Numeric(Numeric::Nanosecond, Pad::Zero) => \"SSSSSS\".to_string(), // Microseconds\n            Item::Fixed(Fixed::ShortMonthName) => \"MMM\".to_string(),\n            Item::Fixed(Fixed::LongMonthName) => \"MMMM\".to_string(),\n            Item::Fixed(Fixed::ShortWeekdayName) => \"EEE\".to_string(),\n            Item::Fixed(Fixed::LongWeekdayName) => \"EEEE\".to_string(),\n            Item::Fixed(Fixed::UpperAmPm) => \"aa\".to_string(),\n            Item::Fixed(Fixed::RFC3339) => \"yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'\".to_string(),\n            Item::Literal(literal) => {\n                // literals are split at every non alphanumeric character\n                if literal.chars().any(|c| c.is_ascii_alphanumeric()) {\n                    // If the literal contains alphanumeric characters, we need to quote it\n                    // to avoid it being interpreted as a pattern understood by Clickhouse.\n                    // Clickhouse uses backticks around\n                    format!(\"'{literal}'\")\n                } else {\n                    literal.replace('\\'', \"\\\\'\\\\'\")\n                }\n            }\n            Item::Space(spaces) => spaces.to_string(),\n            _ => {\n                return Err(Error::new_simple(\n                    \"PRQL doesn't support this format specifier\",\n                ))\n            }\n        })\n    }\n}\n\nimpl DialectHandler for BigQueryDialect {\n    fn ident_quote(&self) -> char {\n        '`'\n    }\n    fn column_exclude(&self) -> Option<ColumnExclude> {\n        // https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_except\n        Some(ColumnExclude::Except)\n    }\n\n    fn set_ops_distinct(&self) -> bool {\n        // https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#set_operators\n        true\n    }\n\n    fn prefers_subquery_parentheses_shorthand(&self) -> bool {\n        true\n    }\n\n    // https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#format_elements_date_time\n    fn translate_chrono_item<'a>(&self, item: Item) -> Result<String> {\n        Ok(match item {\n            Item::Numeric(Numeric::Year, Pad::Zero) => \"%Y\".to_string(),\n            Item::Numeric(Numeric::YearMod100, Pad::Zero) => \"%y\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::Zero) => \"%m\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::Zero) => \"%d\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::Zero) => \"%H\".to_string(),\n            Item::Numeric(Numeric::Hour12, Pad::Zero) => \"%I\".to_string(),\n            Item::Numeric(Numeric::Minute, Pad::Zero) => \"%M\".to_string(),\n            Item::Numeric(Numeric::Second, Pad::Zero) => \"%S\".to_string(),\n            Item::Fixed(Fixed::ShortMonthName) => \"%b\".to_string(),\n            Item::Fixed(Fixed::LongMonthName) => \"%B\".to_string(),\n            Item::Fixed(Fixed::ShortWeekdayName) => \"%a\".to_string(),\n            Item::Fixed(Fixed::LongWeekdayName) => \"%A\".to_string(),\n            Item::Fixed(Fixed::UpperAmPm) => \"%p\".to_string(),\n            Item::Fixed(Fixed::RFC3339) => \"%Y-%m-%dT%H:%M:%S%Ez\".to_string(),\n            Item::Literal(literal) => literal.replace('\\'', \"''\").replace('%', \"%%\"),\n            Item::Space(spaces) => spaces.to_string(),\n            item => {\n                return Err(Error::new_simple(format!(\n                    \"format specifier `{}` is not supported for BigQuery\",\n                    chrono_item_to_strftime(&item),\n                )))\n            }\n        })\n    }\n}\n\nimpl DialectHandler for SnowflakeDialect {\n    fn ident_quoting_style(&self) -> IdentQuotingStyle {\n        // Due to snowflake's identifier casing rules, identifiers are always quoted\n        // https://docs.snowflake.com/en/sql-reference/identifiers-syntax#label-identifier-casing\n        IdentQuotingStyle::AlwaysQuoted\n    }\n\n    fn column_exclude(&self) -> Option<ColumnExclude> {\n        // https://docs.snowflake.com/en/sql-reference/sql/select.html\n        Some(ColumnExclude::Exclude)\n    }\n\n    fn set_ops_distinct(&self) -> bool {\n        // https://docs.snowflake.com/en/sql-reference/operators-query.html\n        false\n    }\n\n    fn interval_quoting_style(&self, _dtf: &DateTimeField) -> IntervalQuotingStyle {\n        // Snowflake requires quotes around value and unit together\n        // https://docs.snowflake.com/en/sql-reference/data-types-datetime#interval-constants\n        IntervalQuotingStyle::ValueAndUnitQuoted\n    }\n\n    fn requires_order_by_in_window_function(&self) -> bool {\n        // https://docs.snowflake.com/en/sql-reference/functions/row_number\n        // ROW_NUMBER() requires ORDER BY in window specification\n        true\n    }\n}\n\nimpl DialectHandler for DuckDbDialect {\n    fn column_exclude(&self) -> Option<ColumnExclude> {\n        // https://duckdb.org/2022/05/04/friendlier-sql.html#select--exclude\n        Some(ColumnExclude::Exclude)\n    }\n\n    fn except_all(&self) -> bool {\n        // https://duckdb.org/docs/sql/query_syntax/setops.html\n        false\n    }\n\n    fn supports_distinct_on(&self) -> bool {\n        true\n    }\n\n    // https://duckdb.org/docs/sql/functions/dateformat\n    fn translate_chrono_item<'a>(&self, item: Item) -> Result<String> {\n        Ok(match item {\n            Item::Numeric(Numeric::Year, Pad::Zero) => \"%Y\".to_string(),\n            Item::Numeric(Numeric::YearMod100, Pad::Zero) => \"%y\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::None) => \"%-m\".to_string(),\n            Item::Numeric(Numeric::Month, Pad::Zero) => \"%m\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::None) => \"%-d\".to_string(),\n            Item::Numeric(Numeric::Day, Pad::Zero) => \"%d\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::None) => \"%-H\".to_string(),\n            Item::Numeric(Numeric::Hour, Pad::Zero) => \"%H\".to_string(),\n            Item::Numeric(Numeric::Hour12, Pad::Zero) => \"%I\".to_string(),\n            Item::Numeric(Numeric::Minute, Pad::Zero) => \"%M\".to_string(),\n            Item::Numeric(Numeric::Second, Pad::Zero) => \"%S\".to_string(),\n            Item::Numeric(Numeric::Nanosecond, Pad::Zero) => \"%f\".to_string(), // Microseconds\n            Item::Fixed(Fixed::ShortMonthName) => \"%b\".to_string(),\n            Item::Fixed(Fixed::LongMonthName) => \"%B\".to_string(),\n            Item::Fixed(Fixed::ShortWeekdayName) => \"%a\".to_string(),\n            Item::Fixed(Fixed::LongWeekdayName) => \"%A\".to_string(),\n            Item::Fixed(Fixed::UpperAmPm) => \"%p\".to_string(),\n            Item::Fixed(Fixed::RFC3339) => \"%Y-%m-%dT%H:%M:%S.%fZ\".to_string(),\n            Item::Literal(literal) => literal.replace('\\'', \"''\").replace('%', \"%%\"),\n            Item::Space(spaces) => spaces.to_string(),\n            _ => {\n                return Err(Error::new_simple(\n                    \"PRQL doesn't support this format specifier\",\n                ))\n            }\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::str::FromStr;\n\n    use chrono::format::{Fixed, Item, Numeric, Pad};\n    use insta::{assert_debug_snapshot, assert_snapshot};\n\n    use super::{chrono_item_to_strftime, BigQueryDialect, Dialect, DialectHandler};\n\n    #[test]\n    fn test_dialect_from_str() {\n        assert_debug_snapshot!(Dialect::from_str(\"postgres\"), @r\"\n        Ok(\n            Postgres,\n        )\n        \");\n\n        assert_debug_snapshot!(Dialect::from_str(\"foo\"), @r\"\n        Err(\n            VariantNotFound,\n        )\n        \");\n    }\n\n    // -- chrono_item_to_strftime tests --\n\n    #[test]\n    fn chrono_item_to_strftime_numerics_zero_pad() {\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Year, Pad::Zero)), @\"%Y\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::YearMod100, Pad::Zero)), @\"%y\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Month, Pad::Zero)), @\"%m\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Day, Pad::Zero)), @\"%d\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Hour, Pad::Zero)), @\"%H\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Hour12, Pad::Zero)), @\"%I\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Minute, Pad::Zero)), @\"%M\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Second, Pad::Zero)), @\"%S\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Nanosecond, Pad::Zero)), @\"%f\");\n    }\n\n    #[test]\n    fn chrono_item_to_strftime_numerics_no_pad() {\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Day, Pad::None)), @\"%-d\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Month, Pad::None)), @\"%-m\");\n    }\n\n    #[test]\n    fn chrono_item_to_strftime_numerics_space_pad() {\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Day, Pad::Space)), @\"%_d\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Numeric(Numeric::Hour, Pad::Space)), @\"%_H\");\n    }\n\n    #[test]\n    fn chrono_item_to_strftime_numeric_unknown() {\n        // Numeric variants not in the explicit list fall through to Debug format\n        let result = chrono_item_to_strftime(&Item::Numeric(Numeric::Ordinal, Pad::Zero));\n        assert!(result.contains(\"Ordinal\"), \"got: {result}\");\n    }\n\n    #[test]\n    fn chrono_item_to_strftime_fixed() {\n        assert_snapshot!(chrono_item_to_strftime(&Item::Fixed(Fixed::ShortMonthName)), @\"%b\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Fixed(Fixed::LongMonthName)), @\"%B\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Fixed(Fixed::ShortWeekdayName)), @\"%a\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Fixed(Fixed::LongWeekdayName)), @\"%A\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Fixed(Fixed::UpperAmPm)), @\"%p\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Fixed(Fixed::LowerAmPm)), @\"%P\");\n        assert_snapshot!(chrono_item_to_strftime(&Item::Fixed(Fixed::RFC3339)), @\"%+\");\n    }\n\n    #[test]\n    fn chrono_item_to_strftime_fixed_unknown() {\n        // Fixed variants not in the explicit list fall through to Debug format\n        let result = chrono_item_to_strftime(&Item::Fixed(Fixed::TimezoneOffsetColon));\n        assert!(result.contains(\"TimezoneOffsetColon\"), \"got: {result}\");\n    }\n\n    #[test]\n    fn chrono_item_to_strftime_non_numeric_non_fixed() {\n        // Literal and Space items fall through to Debug format\n        let result = chrono_item_to_strftime(&Item::Literal(\"-\"));\n        assert!(result.contains(\"Literal\"), \"got: {result}\");\n\n        let result = chrono_item_to_strftime(&Item::Space(\" \"));\n        assert!(result.contains(\"Space\"), \"got: {result}\");\n    }\n\n    // -- BigQueryDialect::translate_chrono_item tests --\n\n    #[test]\n    fn bigquery_translate_numeric_specifiers() {\n        let bq = BigQueryDialect;\n        assert_snapshot!(bq.translate_chrono_item(Item::Numeric(Numeric::Year, Pad::Zero)).unwrap(), @\"%Y\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Numeric(Numeric::YearMod100, Pad::Zero)).unwrap(), @\"%y\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Numeric(Numeric::Month, Pad::Zero)).unwrap(), @\"%m\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Numeric(Numeric::Day, Pad::Zero)).unwrap(), @\"%d\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Numeric(Numeric::Hour, Pad::Zero)).unwrap(), @\"%H\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Numeric(Numeric::Hour12, Pad::Zero)).unwrap(), @\"%I\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Numeric(Numeric::Minute, Pad::Zero)).unwrap(), @\"%M\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Numeric(Numeric::Second, Pad::Zero)).unwrap(), @\"%S\");\n    }\n\n    #[test]\n    fn bigquery_translate_fixed_specifiers() {\n        let bq = BigQueryDialect;\n        assert_snapshot!(bq.translate_chrono_item(Item::Fixed(Fixed::ShortMonthName)).unwrap(), @\"%b\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Fixed(Fixed::LongMonthName)).unwrap(), @\"%B\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Fixed(Fixed::ShortWeekdayName)).unwrap(), @\"%a\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Fixed(Fixed::LongWeekdayName)).unwrap(), @\"%A\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Fixed(Fixed::UpperAmPm)).unwrap(), @\"%p\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Fixed(Fixed::RFC3339)).unwrap(), @\"%Y-%m-%dT%H:%M:%S%Ez\");\n    }\n\n    #[test]\n    fn bigquery_translate_literal() {\n        let bq = BigQueryDialect;\n        assert_snapshot!(bq.translate_chrono_item(Item::Literal(\"-\")).unwrap(), @\"-\");\n        assert_snapshot!(bq.translate_chrono_item(Item::Literal(\"/\")).unwrap(), @\"/\");\n        // Single quotes are escaped by doubling\n        assert_snapshot!(bq.translate_chrono_item(Item::Literal(\"'\")).unwrap(), @\"''\");\n        // Percent signs are escaped by doubling\n        assert_snapshot!(bq.translate_chrono_item(Item::Literal(\"%\")).unwrap(), @\"%%\");\n    }\n\n    #[test]\n    fn bigquery_translate_space() {\n        let bq = BigQueryDialect;\n        assert_snapshot!(bq.translate_chrono_item(Item::Space(\" \")).unwrap(), @\" \");\n        assert_snapshot!(bq.translate_chrono_item(Item::Space(\"  \")).unwrap(), @\"  \");\n    }\n\n    #[test]\n    fn bigquery_translate_unsupported_specifier() {\n        let bq = BigQueryDialect;\n\n        // Nanosecond (%f) is not supported by BigQuery\n        let err = bq\n            .translate_chrono_item(Item::Numeric(Numeric::Nanosecond, Pad::Zero))\n            .unwrap_err();\n        assert_snapshot!(err.to_string(), @r#\"Error { kind: Error, span: None, reason: Simple(\"format specifier `%f` is not supported for BigQuery\"), hints: [], code: None }\"#);\n\n        // Non-zero padding is not supported by BigQuery\n        let err = bq\n            .translate_chrono_item(Item::Numeric(Numeric::Day, Pad::None))\n            .unwrap_err();\n        assert_snapshot!(err.to_string(), @r#\"Error { kind: Error, span: None, reason: Simple(\"format specifier `%-d` is not supported for BigQuery\"), hints: [], code: None }\"#);\n        let err = bq\n            .translate_chrono_item(Item::Numeric(Numeric::Month, Pad::None))\n            .unwrap_err();\n        assert_snapshot!(err.to_string(), @r#\"Error { kind: Error, span: None, reason: Simple(\"format specifier `%-m` is not supported for BigQuery\"), hints: [], code: None }\"#);\n\n        // LowerAmPm (%P) is not supported by BigQuery\n        let err = bq\n            .translate_chrono_item(Item::Fixed(Fixed::LowerAmPm))\n            .unwrap_err();\n        assert_snapshot!(err.to_string(), @r#\"Error { kind: Error, span: None, reason: Simple(\"format specifier `%P` is not supported for BigQuery\"), hints: [], code: None }\"#);\n    }\n}\n\n/*\n## Set operations support matrix\n\nSet-ops have quite different support in major SQL dialects. This is an attempt to document it.\n\n| SQL construct                 | SQLite  | BQ     | Postgres | MySQL 8+ | DuckDB\n|-------------------------------|---------|--------|----------|----------|--------\n| UNION (implicit DISTINCT)     | x       |        | x        | x        | x\n| UNION DISTINCT                |         | x      | x        | x        | x\n| UNION ALL                     | x       | x      | x        | x        | x\n| EXCEPT (implicit DISTINCT)    | x       |        | x        | x        | x\n| EXCEPT DISTINCT               |         | x      | x        | x        | x\n| EXCEPT ALL                    |         |        | x        | x        |\n\n\n### UNION DISTINCT\n\nFor UNION, these are equivalent:\n- a UNION DISTINCT b,\n- DISTINCT (a UNION ALL b)\n- DISTINCT (a UNION ALL (DISTINCT b))\n- DISTINCT ((DISTINCT a) UNION ALL b)\n- DISTINCT ((DISTINCT a) UNION ALL (DISTINCT b))\n\n\n### EXCEPT DISTINCT\n\nFor EXCEPT it makes a difference when DISTINCT is applied. Below is a test query to validate\nthe behavior. When applied before EXCEPT, the output should be [3] and when applied after EXCEPT,\nthe output should be [2, 3].\n\n```\nSELECT * FROM (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 2 UNION ALL SELECT 3) t\nEXCEPT\nSELECT * FROM (SELECT 1 UNION ALL SELECT 2) t;\n```\n\nAll dialects seem to be applying *before*, but none seem to document that.\n\n\n### INTERSECT DISTINCT\n\nFor INTERSECT, it does not matter when DISTINCT is applied. BigQuery documentation does mention\nit is applied *after*, which makes me think there is a difference I'm not seeing.\n\nMy reasoning is that:\n- Distinct is equivalent to applying `group * (take 1)`.\n- In effect, this is a restriction that \"each group can have at most one value\".\n- If we apply DISTINCT to any input of INTERSECT ALL, this restriction on the input is retained\n  through the operation. That's because no group will not contain more values than it started with,\n  and no group that was present in both inputs, will be missing from the output.\n- Thus, applying distinct after INTERSECT ALL is equivalent to applying it to any of the inputs.\n\n*/\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/gen_expr.rs",
    "content": "//! Contains functions that compile [crate::pr::pl] nodes into [sqlparser] nodes.\n\nuse std::cmp::Ordering;\n\nuse itertools::Itertools;\nuse prqlc_parser::generic::{InterpolateItem, Range};\nuse regex::Regex;\nuse sqlparser::ast::{\n    self as sql_ast, BinaryOperator, DateTimeField, Fetch, Function, FunctionArg, FunctionArgExpr,\n    FunctionArgumentList, ObjectName, OrderByExpr, SelectItem, UnaryOperator, Value,\n    WindowFrameBound, WindowSpec,\n};\n\nuse super::gen_projection::try_into_exprs;\nuse super::{keywords, Context};\nuse crate::ir::generic::{ColumnSort, SortDirection, WindowFrame, WindowKind};\nuse crate::ir::pl::{self, Ident, Literal};\nuse crate::ir::rq;\nuse crate::sql::dialect::{IdentQuotingStyle, IntervalQuotingStyle};\nuse crate::sql::pq::context::ColumnDecl;\nuse crate::utils::{valid_ident, OrMap};\nuse crate::{Error, Result, Span, WithErrorInfo};\n\npub(super) fn translate_expr(expr: rq::Expr, ctx: &mut Context) -> Result<ExprOrSource> {\n    Ok(match expr.kind {\n        rq::ExprKind::ColumnRef(cid) => translate_cid(cid, ctx)?,\n\n        // Fairly hacky — convert everything to a string, then concat it,\n        // then convert to sql_ast::Expr. We can't use the `Item::sql_ast::Expr` code above\n        // since we don't want to intersperse with spaces.\n        rq::ExprKind::SString(s_string_items) => {\n            let text = translate_sstring(s_string_items, ctx)?;\n\n            ExprOrSource::Source(SourceExpr {\n                text,\n                binding_strength: 100,\n                window_frame: false,\n            })\n        }\n        rq::ExprKind::Param(id) => ExprOrSource::Source(SourceExpr {\n            text: format!(\"${id}\"),\n            binding_strength: 100,\n            window_frame: false,\n        }),\n        rq::ExprKind::Literal(l) => translate_literal(l, ctx)?.into(),\n        rq::ExprKind::Case(mut cases) => {\n            let default = cases\n                .last()\n                .filter(|last| {\n                    matches!(\n                        last.condition.kind,\n                        rq::ExprKind::Literal(Literal::Boolean(true))\n                    )\n                })\n                .map(|def| translate_expr(def.value.clone(), ctx))\n                .transpose()?\n                .map(|x| x.into_ast());\n\n            if default.is_some() {\n                cases.pop();\n            }\n\n            let else_result = default\n                .or(Some(sql_ast::Expr::Value(Value::Null.into())))\n                .map(Box::new);\n\n            let conditions = cases\n                .into_iter()\n                .map(|case| -> Result<_> {\n                    let condition = translate_expr(case.condition, ctx)?.into_ast();\n                    let result = translate_expr(case.value, ctx)?.into_ast();\n                    Ok(sql_ast::CaseWhen { condition, result })\n                })\n                .try_collect()?;\n\n            sql_ast::Expr::Case {\n                case_token: sqlparser::ast::helpers::attached_token::AttachedToken::empty(),\n                end_token: sqlparser::ast::helpers::attached_token::AttachedToken::empty(),\n                operand: None,\n                conditions,\n                else_result,\n            }\n            .into()\n        }\n        rq::ExprKind::Operator { ref name, ref args } => {\n            // A few special cases and then fall-through to the standard approach.\n            match name.as_str() {\n                // See notes in `std.rs` re whether we use names vs.\n                // `FunctionDecl` vs. an Enum; and getting the correct\n                // number of args from there. Currently the error messages\n                // for the wrong number of args will be bad (though it's an\n                // unusual case where RQ contains something like `std.eq`\n                // with the wrong number of args).\n                \"std.eq\" | \"std.ne\" => {\n                    if let [a, b] = args.as_slice() {\n                        if a.kind == rq::ExprKind::Literal(Literal::Null)\n                            || b.kind == rq::ExprKind::Literal(Literal::Null)\n                        {\n                            return Ok(process_null(name, args, ctx)?.into());\n                        } else {\n                            let op = operator_from_name(name).unwrap();\n                            return Ok(translate_binary_operator(a, b, op, ctx)?.into());\n                        }\n                    }\n                }\n                \"std.concat\" => return Ok(process_concat(&expr, ctx)?.into()),\n                \"std.array_in\" => return Ok(process_array_in(&expr, args, ctx)?.into()),\n                \"std.date.to_text\" => {\n                    return Ok(process_date_to_text(&expr, name, args, ctx)?.into())\n                }\n                _ => match try_into_between(expr.clone(), ctx)? {\n                    Some(between_expr) => return Ok(between_expr.into()),\n                    None => {\n                        if let Some(op) = operator_from_name(name) {\n                            if let [left, right] = args.as_slice() {\n                                return Ok(translate_binary_operator(left, right, op, ctx)?.into());\n                            }\n                        }\n                    }\n                },\n            }\n            super::operators::translate_operator_expr(expr, ctx)?\n        }\n        rq::ExprKind::Array(exprs) => {\n            let elements = exprs\n                .iter()\n                .map(|e| translate_expr(e.clone(), ctx).map(|x| x.into_ast()))\n                .try_collect()?;\n\n            let sql_array = ctx.dialect.translate_sql_array(elements)?;\n\n            // Return as SourceExpr so it can be interpolated into s-strings\n            ExprOrSource::Source(SourceExpr {\n                text: sql_array.to_string(),\n                binding_strength: 100,\n                window_frame: false,\n            })\n        }\n    })\n}\n\n/// Translates into IS NULL if possible\nfn process_null(name: &str, args: &[rq::Expr], ctx: &mut Context) -> Result<sql_ast::Expr> {\n    let (a, b) = (&args[0], &args[1]);\n    let operand = if matches!(a.kind, rq::ExprKind::Literal(Literal::Null)) {\n        b\n    } else {\n        a\n    };\n\n    // If this were an Enum, we could match on it (see notes in `std.rs`).\n    if name == \"std.eq\" {\n        let strength = sql_ast::Expr::IsNull(Box::new(sql_ast::Expr::Value(Value::Null.into())))\n            .binding_strength();\n        let expr = translate_operand(operand.clone(), true, strength, Associativity::Both, ctx)?;\n        let expr = Box::new(expr.into_ast());\n        Ok(sql_ast::Expr::IsNull(expr))\n    } else if name == \"std.ne\" {\n        let strength = sql_ast::Expr::IsNotNull(Box::new(sql_ast::Expr::Value(Value::Null.into())))\n            .binding_strength();\n        let expr = translate_operand(operand.clone(), true, strength, Associativity::Both, ctx)?;\n        let expr = Box::new(expr.into_ast());\n        Ok(sql_ast::Expr::IsNotNull(expr))\n    } else {\n        unreachable!()\n    }\n}\n\n/// Translates into IN (v1, v2, ...) if possible\nfn process_array_in(\n    expr: &rq::Expr,\n    args: &[rq::Expr],\n    ctx: &mut Context,\n) -> Result<sql_ast::Expr> {\n    match args {\n        [col_expr @ rq::Expr {\n            kind:\n                rq::ExprKind::ColumnRef(_)\n                | rq::ExprKind::Literal(_)\n                | rq::ExprKind::SString(_)\n                | rq::ExprKind::Param(_)\n                | rq::ExprKind::Operator { name: _, args: _ },\n            ..\n        }, rq::Expr {\n            kind: rq::ExprKind::Array(in_values),\n            ..\n        }] => {\n            if in_values.is_empty() {\n                // We avoid producing `in ()` expressions since they are not syntactically valid\n                // in some engines like PostgreSQL or MySQL.\n                // We can instead optimize this to a condition that is always false\n                Ok(sql_ast::Expr::Value(Value::Boolean(false).into()))\n            } else {\n                Ok(sql_ast::Expr::InList {\n                    expr: Box::new(translate_expr(col_expr.clone(), ctx)?.into_ast()),\n                    list: in_values\n                        .iter()\n                        .map(|a| Ok(translate_expr(a.clone(), ctx)?.into_ast()))\n                        .collect::<Result<Vec<sql_ast::Expr>>>()?,\n                    negated: false,\n                })\n            }\n        }\n        _ => Err(\n            Error::new_simple(\"args to `std.array_in` must be an expression and an array\")\n                .with_span(expr.span),\n        ),\n    }\n}\n\n/// Translates PRQL date format (based on `chrono` crate) to dialect specific date format\n/// For now only date format as string literal is supported\nfn process_date_to_text(\n    expr: &rq::Expr,\n    op_name: &str,\n    args: &[rq::Expr],\n    ctx: &mut Context,\n) -> Result<sql_ast::Expr> {\n    if let [date_format_exp @ rq::Expr {\n        kind: rq::ExprKind::Literal(Literal::String(date_format)),\n        ..\n    }, col_expr] = args\n    {\n        let expr = rq::Expr {\n            kind: rq::ExprKind::Operator {\n                name: op_name.to_string(),\n                args: vec![\n                    rq::Expr {\n                        kind: rq::ExprKind::Literal(Literal::String(\n                            ctx.dialect\n                                .translate_prql_date_format(date_format)\n                                .map_err(|e| e.with_span(date_format_exp.span))?,\n                        )),\n                        span: date_format_exp.span,\n                    },\n                    col_expr.clone(),\n                ],\n            },\n            ..expr.clone()\n        };\n        Ok(super::operators::translate_operator_expr(expr, ctx)?.into_ast())\n    } else {\n        Err(\n            Error::new_simple(\"`std.date.to_text` only supports a string literal as format\")\n                .with_span(expr.span),\n        )\n    }\n}\n\nfn process_concat(expr: &rq::Expr, ctx: &mut Context) -> Result<sql_ast::Expr> {\n    if ctx.dialect.has_concat_function() {\n        let concat_args = collect_concat_args(expr);\n\n        let args_list = concat_args\n            .iter()\n            .map(|a| {\n                translate_expr((*a).clone(), ctx)\n                    .map(|x| FunctionArg::Unnamed(FunctionArgExpr::Expr(x.into_ast())))\n            })\n            .try_collect()?;\n\n        let args = sql_ast::FunctionArguments::List(FunctionArgumentList {\n            args: args_list,\n            clauses: vec![],\n            duplicate_treatment: None,\n        });\n\n        Ok(sql_ast::Expr::Function(Function {\n            name: ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(\n                sql_ast::Ident::new(\"CONCAT\"),\n            )]),\n            args,\n            over: None,\n            filter: None,\n            null_treatment: None,\n            within_group: vec![],\n            parameters: sql_ast::FunctionArguments::None,\n            uses_odbc_syntax: false,\n        }))\n    } else {\n        let concat_args = collect_concat_args(expr);\n\n        let mut iter = concat_args.into_iter();\n        let first_expr = iter.next().unwrap();\n        let mut current_expr = translate_expr(first_expr.clone(), ctx)?.into_ast();\n\n        for arg in iter {\n            let translated_arg = translate_expr(arg.clone(), ctx)?.into_ast();\n            current_expr = sql_ast::Expr::BinaryOp {\n                left: Box::new(current_expr),\n                op: BinaryOperator::StringConcat,\n                right: Box::new(translated_arg),\n            };\n        }\n\n        Ok(current_expr)\n    }\n}\n\nfn translate_binary_operator(\n    left: &rq::Expr,\n    right: &rq::Expr,\n    op: BinaryOperator,\n    ctx: &mut Context,\n) -> Result<sql_ast::Expr> {\n    let strength = op.binding_strength();\n\n    let left = translate_operand(left.clone(), true, strength, op.associativity(), ctx)?;\n    let right = translate_operand(right.clone(), false, strength, op.associativity(), ctx)?;\n\n    let left = Box::new(left.into_ast());\n    let right = Box::new(right.into_ast());\n\n    Ok(sql_ast::Expr::BinaryOp { left, op, right })\n}\n\nfn collect_concat_args(expr: &rq::Expr) -> Vec<&rq::Expr> {\n    match &expr.kind {\n        rq::ExprKind::Operator { name, args } if name == \"std.concat\" => {\n            args.iter().flat_map(collect_concat_args).collect()\n        }\n        _ => vec![expr],\n    }\n}\n\n/// Translate expr into a BETWEEN statement if possible, otherwise returns the expr unchanged.\nfn try_into_between(expr: rq::Expr, ctx: &mut Context) -> Result<Option<sql_ast::Expr>> {\n    match expr.kind {\n        rq::ExprKind::Operator { name, args } if name == \"std.and\" => {\n            let [a, b]: [_; 2] = args.try_into().unwrap();\n\n            match (a.kind, b.kind) {\n                (\n                    rq::ExprKind::Operator {\n                        name: a_name,\n                        args: a_args,\n                    },\n                    rq::ExprKind::Operator {\n                        name: b_name,\n                        args: b_args,\n                    },\n                ) if a_name == \"std.gte\" && b_name == \"std.lte\" => {\n                    let [a_l, a_r]: [_; 2] = a_args.try_into().unwrap();\n                    let [b_l, b_r]: [_; 2] = b_args.try_into().unwrap();\n\n                    // We need for the values on each arm to be the same; e.g. x\n                    // > 3 and x < 5\n                    if a_l == b_l {\n                        return Ok(Some(sql_ast::Expr::Between {\n                            expr: Box::new(\n                                translate_operand(a_l, true, 0, Associativity::Both, ctx)?\n                                    .into_ast(),\n                            ),\n                            negated: false,\n                            low: Box::new(\n                                translate_operand(a_r, true, 0, Associativity::Both, ctx)?\n                                    .into_ast(),\n                            ),\n                            high: Box::new(\n                                translate_operand(b_r, true, 0, Associativity::Both, ctx)?\n                                    .into_ast(),\n                            ),\n                        }));\n                    }\n                }\n                _ => (),\n            }\n        }\n        _ => (),\n    }\n    Ok(None)\n}\n\nfn operator_from_name(name: &str) -> Option<BinaryOperator> {\n    use BinaryOperator::*;\n    match name {\n        \"std.mul\" => Some(Multiply),\n        \"std.add\" => Some(Plus),\n        \"std.sub\" => Some(Minus),\n        \"std.eq\" => Some(Eq),\n        \"std.ne\" => Some(NotEq),\n        \"std.gt\" => Some(Gt),\n        \"std.lt\" => Some(Lt),\n        \"std.gte\" => Some(GtEq),\n        \"std.lte\" => Some(LtEq),\n        \"std.and\" => Some(And),\n        \"std.or\" => Some(Or),\n        \"std.concat\" => Some(StringConcat),\n        _ => None,\n    }\n}\n\npub(super) fn translate_literal(l: Literal, ctx: &Context) -> Result<sql_ast::Expr> {\n    Ok(match l {\n        Literal::Null => sql_ast::Expr::Value(Value::Null.into()),\n        Literal::String(s) | Literal::RawString(s) => {\n            sql_ast::Expr::Value(Value::SingleQuotedString(s).into())\n        }\n        Literal::Boolean(b) => sql_ast::Expr::Value(Value::Boolean(b).into()),\n        Literal::Float(f) => sql_ast::Expr::Value(Value::Number(format!(\"{f:?}\"), false).into()),\n        Literal::Integer(i) => sql_ast::Expr::Value(Value::Number(format!(\"{i}\"), false).into()),\n        Literal::Date(value) => translate_datetime_literal(sql_ast::DataType::Date, value, ctx),\n        Literal::Time(value) => translate_datetime_literal(\n            sql_ast::DataType::Time(None, sql_ast::TimezoneInfo::None),\n            value,\n            ctx,\n        ),\n        Literal::Timestamp(value) => translate_datetime_literal(\n            sql_ast::DataType::Timestamp(None, sql_ast::TimezoneInfo::None),\n            value,\n            ctx,\n        ),\n        Literal::ValueAndUnit(vau) => {\n            let sql_parser_datetime = match vau.unit.as_str() {\n                \"years\" => DateTimeField::Year,\n                \"months\" => DateTimeField::Month,\n                \"weeks\" => DateTimeField::Week(None),\n                \"days\" => DateTimeField::Day,\n                \"hours\" => DateTimeField::Hour,\n                \"minutes\" => DateTimeField::Minute,\n                \"seconds\" => DateTimeField::Second,\n                \"milliseconds\" => DateTimeField::Millisecond,\n                \"microseconds\" => DateTimeField::Microsecond,\n                _ => {\n                    return Err(Error::new_simple(format!(\n                        \"Unsupported interval unit: {}\",\n                        vau.unit\n                    )))\n                }\n            };\n            match ctx.dialect.interval_quoting_style(&sql_parser_datetime) {\n                IntervalQuotingStyle::ValueAndUnitQuoted => {\n                    //postgres requires quotes around number and unit together eg '3 WEEK'\n                    let value = Box::new(sql_ast::Expr::Value(\n                        Value::SingleQuotedString(format!(\"{} {}\", vau.n, sql_parser_datetime))\n                            .into(),\n                    ));\n                    sql_ast::Expr::Interval(sqlparser::ast::Interval {\n                        value,\n                        leading_field: None, //set to none since field is now contained in string\n                        leading_precision: None,\n                        last_field: None,\n                        fractional_seconds_precision: None,\n                    })\n                }\n                IntervalQuotingStyle::NoQuotes => {\n                    let value = Box::new(translate_literal(Literal::Integer(vau.n), ctx)?);\n                    sql_ast::Expr::Interval(sqlparser::ast::Interval {\n                        value,\n                        leading_field: Some(sql_parser_datetime),\n                        leading_precision: None,\n                        last_field: None,\n                        fractional_seconds_precision: None,\n                    })\n                }\n                // Redshift requires quotes around the number only, otherwise months and years are\n                // not supported. eg '3' MONTH\n                IntervalQuotingStyle::ValueQuoted => {\n                    // Adding single quotes around the number\n                    let value = Box::new(sql_ast::Expr::Value(\n                        Value::SingleQuotedString(vau.n.to_string()).into(),\n                    ));\n                    sql_ast::Expr::Interval(sqlparser::ast::Interval {\n                        value,\n                        leading_field: Some(sql_parser_datetime),\n                        leading_precision: None,\n                        last_field: None,\n                        fractional_seconds_precision: None,\n                    })\n                }\n            }\n        }\n    })\n}\n\nfn translate_datetime_literal(\n    data_type: sql_ast::DataType,\n    value: String,\n    ctx: &Context,\n) -> sql_ast::Expr {\n    if ctx.dialect.is::<crate::sql::dialect::SQLiteDialect>() {\n        translate_datetime_literal_with_sqlite_function(data_type, value)\n    } else {\n        translate_datetime_literal_with_typed_string(data_type, value)\n    }\n}\n\nfn translate_datetime_literal_with_typed_string(\n    data_type: sql_ast::DataType,\n    value: String,\n) -> sql_ast::Expr {\n    sql_ast::Expr::TypedString(sqlparser::ast::TypedString {\n        data_type,\n        value: sqlparser::ast::Value::SingleQuotedString(value).into(),\n        uses_odbc_syntax: false,\n    })\n}\n\nfn translate_datetime_literal_with_sqlite_function(\n    data_type: sql_ast::DataType,\n    value: String,\n) -> sql_ast::Expr {\n    // TODO: promote parsing timezone handling to the parser; we should be storing\n    // structured data rather than strings in the AST\n    let timezone_indicator_regex = Regex::new(r\"([+-]\\d{2}):?(\\d{2})$\").unwrap();\n    let time_value = if let Some(groups) = timezone_indicator_regex.captures(value.as_str()) {\n        // formalize the timezone indicator to be [+-]HH:MM\n        // ref: https://www.sqlite.org/lang_datefunc.html\n        timezone_indicator_regex\n            .replace(&value, format!(\"{}:{}\", &groups[1], &groups[2]).as_str())\n            .to_string()\n    } else {\n        value\n    };\n\n    let arg = FunctionArg::Unnamed(FunctionArgExpr::Expr(sql_ast::Expr::Value(\n        Value::SingleQuotedString(time_value).into(),\n    )));\n\n    let func_name = match data_type {\n        sql_ast::DataType::Date => data_type.to_string(),\n        sql_ast::DataType::Time(..) => data_type.to_string(),\n        sql_ast::DataType::Timestamp(..) => \"DATETIME\".to_string(),\n        _ => unreachable!(),\n    };\n\n    let args = sql_ast::FunctionArguments::List(FunctionArgumentList {\n        args: vec![arg],\n        clauses: vec![],\n        duplicate_treatment: None,\n    });\n\n    sql_ast::Expr::Function(Function {\n        name: ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(\n            sql_ast::Ident::new(func_name),\n        )]),\n        args,\n        over: None,\n        filter: None,\n        null_treatment: None,\n        within_group: vec![],\n        parameters: sql_ast::FunctionArguments::None,\n        uses_odbc_syntax: false,\n    })\n}\n\npub(super) fn translate_cid(cid: rq::CId, ctx: &mut Context) -> Result<ExprOrSource> {\n    if ctx.query.pre_projection {\n        log::debug!(\"translating {cid:?} pre projection\");\n        let decl = ctx.anchor.column_decls.get(&cid).expect(\"bad RQ ids\");\n\n        Ok(match decl {\n            ColumnDecl::Compute(compute) => {\n                let window = compute.window.clone();\n                let span = compute.expr.span;\n\n                let prev_wf = ctx.query.window_function;\n                ctx.query.window_function = window.is_some();\n                let expr = translate_expr(compute.expr.clone(), ctx)?;\n                ctx.query.window_function = prev_wf;\n\n                if let Some(window) = window {\n                    translate_windowed(expr, window, ctx, span)?\n                } else {\n                    expr\n                }\n            }\n            ColumnDecl::RelationColumn(riid, _, col) => {\n                let column = match col.clone() {\n                    rq::RelationColumn::Wildcard => translate_star(ctx, None)?,\n                    rq::RelationColumn::Single(name) => name.unwrap(),\n                };\n                let t = &ctx.anchor.relation_instances[riid];\n\n                let table_ident = t.table_ref.name.clone().map(Ident::from_name);\n                let ident = translate_ident(table_ident, Some(column), ctx);\n                sql_ast::Expr::CompoundIdentifier(ident).into()\n            }\n        })\n    } else {\n        // translate into ident\n        let column_decl = &&ctx.anchor.column_decls[&cid];\n\n        let table_name = if let ColumnDecl::RelationColumn(riid, _, _) = column_decl {\n            let t = &ctx.anchor.relation_instances[riid];\n            Some(t.table_ref.name.clone().unwrap())\n        } else {\n            None\n        };\n\n        let column = match &column_decl {\n            ColumnDecl::RelationColumn(_, _, rq::RelationColumn::Wildcard) => {\n                translate_star(ctx, None)?\n            }\n\n            _ => {\n                let name = ctx.anchor.column_names.get(&cid).cloned();\n                name.expect(\"name of this column has not been to be set before generating SQL\")\n            }\n        };\n\n        let ident = translate_ident(table_name.map(Ident::from_name), Some(column), ctx);\n\n        log::debug!(\"translating {cid:?} post projection: {ident:?}\");\n\n        let ident = sql_ast::Expr::CompoundIdentifier(ident);\n        Ok(ident.into())\n    }\n}\n\npub(super) fn translate_star(ctx: &Context, span: Option<Span>) -> Result<String> {\n    if !ctx.query.allow_stars {\n        Err(\n            Error::new_simple(\"Target dialect does not support * in this position.\")\n                .with_span(span),\n        )\n    } else {\n        Ok(\"*\".to_string())\n    }\n}\n\npub(super) fn translate_sstring(\n    items: Vec<InterpolateItem<rq::Expr>>,\n    ctx: &mut Context,\n) -> Result<String> {\n    Ok(items\n        .into_iter()\n        .map(|s_string_item| match s_string_item {\n            InterpolateItem::String(string) => Ok(string),\n            InterpolateItem::Expr { expr, .. } => {\n                translate_expr(*expr, ctx).map(|expr| expr.into_source())\n            }\n        })\n        .collect::<Result<Vec<String>>>()?\n        .join(\"\"))\n}\n\n/// Aggregate several ordered ranges into one, computing the intersection.\n///\n/// Returns a tuple of `(start, end)`, where `end` is optional.\npub(super) fn range_of_ranges(ranges: Vec<Range<rq::Expr>>) -> Result<Range<i64>> {\n    let mut current = Range::default();\n    for range in ranges {\n        let mut range = try_range_into_int(range)?;\n\n        // b = b + a.start -1 (take care of 1-based index!)\n        range.start = range.start.or_map(current.start, |a, b| a + b - 1);\n        range.end = range.end.map(|b| current.start.unwrap_or(1) + b - 1);\n\n        // b.end = min(a.end, b.end)\n        range.end = current.end.or_map(range.end, i64::min);\n        current = range;\n    }\n\n    if let Some((s, e)) = current.start.zip(current.end) {\n        if e < s {\n            return Ok(Range {\n                start: None,\n                end: Some(0),\n            });\n        }\n    }\n    Ok(current)\n}\n\nfn unpack_as_int_literal(bound: rq::Expr) -> Result<i64> {\n    Some(bound.kind)\n        .and_then(|x| x.into_literal().ok())\n        .and_then(|x| x.into_integer().ok())\n        .ok_or_else(|| Error::new_simple(\"expected an integer literal\").with_span(bound.span))\n}\n\nfn try_range_into_int(range: Range<rq::Expr>) -> Result<Range<i64>> {\n    Ok(Range {\n        start: range.start.map(unpack_as_int_literal).transpose()?,\n        end: range.end.map(unpack_as_int_literal).transpose()?,\n    })\n}\n\npub(super) fn expr_of_i64(number: i64) -> sql_ast::Expr {\n    sql_ast::Expr::Value(Value::Number(number.to_string(), number.leading_zeros() < 32).into())\n}\n\npub(super) fn fetch_of_i64(take: i64, ctx: &mut Context) -> Fetch {\n    let kind = rq::ExprKind::Literal(Literal::Integer(take));\n    let expr = rq::Expr { kind, span: None };\n    Fetch {\n        quantity: Some(translate_expr(expr, ctx).unwrap().into_ast()),\n        with_ties: false,\n        percent: false,\n    }\n}\n\npub(super) fn translate_select_item(cid: rq::CId, ctx: &mut Context) -> Result<SelectItem> {\n    let expr = translate_cid(cid, ctx)?.into_ast();\n\n    let inferred_name = match &expr {\n        // sql_ast::Expr::Identifier is used for s-strings\n        sql_ast::Expr::CompoundIdentifier(parts) => parts.last().map(|p| &p.value),\n        _ => None,\n    }\n    .filter(|n| *n != \"*\");\n\n    let expected = ctx.anchor.column_names.get(&cid);\n\n    if inferred_name != expected {\n        // use expected name\n        let ident = expected.cloned().unwrap_or_else(|| {\n            // or use something that will not clash with other names\n            ctx.anchor.col_name.gen()\n        });\n        ctx.anchor.column_names.insert(cid, ident.to_string());\n\n        return Ok(SelectItem::ExprWithAlias {\n            alias: translate_ident_part(ident, ctx),\n            expr,\n        });\n    }\n\n    Ok(SelectItem::UnnamedExpr(expr))\n}\n\nfn translate_windowed(\n    expr: ExprOrSource,\n    window: rq::Window,\n    ctx: &mut Context,\n    span: Option<Span>,\n) -> Result<ExprOrSource> {\n    let default_frame = {\n        let (kind, range) = if window.sort.is_empty() {\n            (WindowKind::Rows, Range::unbounded())\n        } else {\n            (\n                WindowKind::Range,\n                Range {\n                    start: None,\n                    end: Some(rq::Expr {\n                        kind: rq::ExprKind::Literal(Literal::Integer(0)),\n                        span: None,\n                    }),\n                },\n            )\n        };\n        WindowFrame { kind, range }\n    };\n\n    let supports_frame = matches!(\n        expr,\n        ExprOrSource::Source(SourceExpr {\n            window_frame: true,\n            ..\n        })\n    );\n\n    let mut order_by: Vec<OrderByExpr> = (window.sort)\n        .into_iter()\n        .map(|sort| translate_column_sort(&sort, ctx))\n        .try_collect()?;\n\n    // Some dialects (e.g., Snowflake) require ORDER BY for window functions\n    // When no ORDER BY is specified, use ORDER BY 1 as a fallback\n    if order_by.is_empty() && ctx.dialect.requires_order_by_in_window_function() {\n        order_by.push(OrderByExpr {\n            expr: sql_ast::Expr::Value(Value::Number(\"1\".to_string(), false).into()),\n            options: sqlparser::ast::OrderByOptions {\n                asc: None,\n                nulls_first: None,\n            },\n            with_fill: None,\n        });\n    }\n\n    let window = WindowSpec {\n        window_name: None,\n        partition_by: try_into_exprs(window.partition, ctx, span)?,\n        order_by,\n        window_frame: if supports_frame && window.frame != default_frame {\n            Some(try_into_window_frame(window.frame)?)\n        } else {\n            None\n        },\n    };\n\n    let expr = expr.into_source();\n    Ok(ExprOrSource::Source(SourceExpr {\n        text: format!(\"{expr} OVER ({window})\"),\n        binding_strength: 100,\n        window_frame: false,\n    }))\n}\n\nfn try_into_window_frame(frame: WindowFrame<rq::Expr>) -> Result<sql_ast::WindowFrame> {\n    fn parse_bound(bound: rq::Expr) -> Result<WindowFrameBound> {\n        let as_int = unpack_as_int_literal(bound)?;\n        Ok(match as_int {\n            0 => WindowFrameBound::CurrentRow,\n            1.. => WindowFrameBound::Following(Some(Box::new(sql_ast::Expr::Value(\n                sql_ast::Value::Number(as_int.to_string(), false).into(),\n            )))),\n            _ => WindowFrameBound::Preceding(Some(Box::new(sql_ast::Expr::Value(\n                sql_ast::Value::Number((-as_int).to_string(), false).into(),\n            )))),\n        })\n    }\n\n    Ok(sql_ast::WindowFrame {\n        units: match frame.kind {\n            WindowKind::Rows => sql_ast::WindowFrameUnits::Rows,\n            WindowKind::Range => sql_ast::WindowFrameUnits::Range,\n        },\n        start_bound: if let Some(start) = frame.range.start {\n            parse_bound(start)?\n        } else {\n            WindowFrameBound::Preceding(None)\n        },\n        end_bound: Some(if let Some(end) = frame.range.end {\n            parse_bound(end)?\n        } else {\n            WindowFrameBound::Following(None)\n        }),\n    })\n}\n\npub(super) fn translate_column_sort(\n    sort: &ColumnSort<rq::CId>,\n    ctx: &mut Context,\n) -> Result<OrderByExpr> {\n    Ok(OrderByExpr {\n        expr: translate_cid(sort.column, ctx)?.into_ast(),\n        options: sqlparser::ast::OrderByOptions {\n            asc: if matches!(sort.direction, SortDirection::Asc) {\n                None // default order is ASC, so there is no need to emit it\n            } else {\n                Some(false)\n            },\n            nulls_first: None,\n        },\n        with_fill: None,\n    })\n}\n\n/// Translate a PRQL Ident to a Vec of SQL Idents.\n// We return a vec of SQL Idents because sqlparser sometimes uses\n// [ObjectName](sql_ast::ObjectName) and sometimes uses\n// [sql_ast::Expr::CompoundIdentifier](sql_ast::Expr::CompoundIdentifier), each of which\n// contains `Vec<Ident>`.\npub(super) fn translate_ident(\n    table_ident: Option<pl::Ident>,\n    column: Option<String>,\n    ctx: &Context,\n) -> Vec<sql_ast::Ident> {\n    let mut parts = Vec::with_capacity(4);\n    if !ctx.query.omit_ident_prefix || column.is_none() {\n        if let Some(table) = table_ident {\n            parts.extend(table);\n        }\n    }\n\n    parts.extend(column);\n\n    parts\n        .into_iter()\n        .map(|x| translate_ident_part(x, ctx))\n        .collect()\n}\n\npub(super) fn translate_ident_part(ident: String, ctx: &Context) -> sql_ast::Ident {\n    let is_bare = valid_ident().is_match(&ident);\n    match ctx.dialect.ident_quoting_style() {\n        IdentQuotingStyle::ConditionallyQuoted => {\n            if is_bare && !keywords::is_keyword(&ident, &ctx.dialect_enum) {\n                sql_ast::Ident::new(ident)\n            } else {\n                sql_ast::Ident::with_quote(ctx.dialect.ident_quote(), ident)\n            }\n        }\n        IdentQuotingStyle::AlwaysQuoted => {\n            sql_ast::Ident::with_quote(ctx.dialect.ident_quote(), ident)\n        }\n    }\n}\n\npub(super) fn translate_operand(\n    expr: rq::Expr,\n    is_left: bool,\n    parent_strength: i32,\n    parent_associativity: Associativity,\n    context: &mut Context,\n) -> Result<ExprOrSource> {\n    let expr = translate_expr(expr, context)?;\n\n    if needs_parentheses(&expr, is_left, parent_strength, parent_associativity) {\n        Ok(expr.wrap_in_parenthesis())\n    } else {\n        Ok(expr)\n    }\n}\n\n/// For an operation represented as `a child b` with a surrounding parent\n/// operation (e.g., `(a child b) parent c` or `a parent (b child c)`):\n///\n/// 1. When the child operator has higher precedence than the parent,\n///    parentheses *are not* required.\n///\n/// 2. When the child operator has lower precedence than the parent,\n///    parentheses *are* required.\n///\n/// 3. When the child and parent operators have the same precedence, the child\n///    is on the {left,right} and the parent is {left,right} associative,\n///    parentheses are not required. Some examples of when parentheses are not required:\n///    - `(a - b) - c` & `(a + b) - c` — as opposed to `a - (b - c)`\n///    - `a + (b - c)` & `a + (b + c)` — as opposed to `a - (b + c)` & `a - (b - c)`\n///\n///\n//\n// If it were possible to evaluate this with less context that would be\n// preferable, but it's not clear how to do that. (For example, even if we\n// passed a reference to the parent, that still wouldn't tell us whether the\n// child were on the left or the right, which is required...)\n//\n// Note that the code is deliberately somewhat verbose. While it could instead\n// be a neat single expression, it was quite difficult to work through, so\n// please do not make the code terser without being confident that it's easier\n// to understand.\nfn needs_parentheses(\n    expr: &ExprOrSource,\n    is_left: bool,\n    parent_strength: i32,\n    parent_associativity: Associativity,\n) -> bool {\n    let rule_3a = matches!(parent_associativity, Associativity::Both);\n    let rule_3b_left = is_left && parent_associativity.left_associative();\n    let rule_3b_right = !is_left && parent_associativity.right_associative();\n\n    match expr.binding_strength().cmp(&parent_strength) {\n        // Rule 1\n        Ordering::Greater => false,\n        // Rule 2\n        Ordering::Less => true,\n        // Rule 3\n        Ordering::Equal => !(rule_3a || rule_3b_left || rule_3b_right),\n    }\n}\n\n/// Associativity of an expression's operator.\n/// Note that there's no exponent symbol in SQL, so we don't seem to require a `Right` variant.\n/// https://en.wikipedia.org/wiki/Operator_associativity\n#[allow(dead_code)]\n#[derive(Debug, PartialEq, Eq)]\npub enum Associativity {\n    Left,\n    /// `Both` means mathematically associative, like `+` or `*`\n    Both,\n    Right,\n}\n\nimpl Associativity {\n    /// Returns true iff `a + b + c = (a + b) + c`\n    fn left_associative(&self) -> bool {\n        matches!(self, Associativity::Left | Associativity::Both)\n    }\n\n    /// Returns true iff `a + b + c = a + (b + c)`\n    fn right_associative(&self) -> bool {\n        matches!(self, Associativity::Right | Associativity::Both)\n    }\n}\n\ntrait SQLExpression {\n    /// Returns binding strength of a SQL expression\n    /// https://www.postgresql.org/docs/14/sql-syntax-lexical.html#id-1.5.3.5.13.2\n    /// https://docs.microsoft.com/en-us/sql/t-sql/language-elements/operator-precedence-transact-sql?view=sql-server-ver16\n    fn binding_strength(&self) -> i32;\n\n    /// Default to `Both`, but expected to be overwritten by concrete types\n    fn associativity(&self) -> Associativity {\n        Associativity::Both\n    }\n}\n\nimpl SQLExpression for sql_ast::Expr {\n    fn binding_strength(&self) -> i32 {\n        // Strength of an expression depends only on the top-level operator, because all\n        // other nested expressions can only have lower strength\n        match self {\n            sql_ast::Expr::BinaryOp { op, .. } => op.binding_strength(),\n\n            sql_ast::Expr::UnaryOp { op, .. } => op.binding_strength(),\n\n            sql_ast::Expr::Like { .. } | sql_ast::Expr::ILike { .. } => 7,\n\n            sql_ast::Expr::IsNull(_) | sql_ast::Expr::IsNotNull(_) => 5,\n\n            // all other items types bind stronger (function calls, literals, ...)\n            _ => 20,\n        }\n    }\n    fn associativity(&self) -> Associativity {\n        match self {\n            sql_ast::Expr::BinaryOp { op, .. } => op.associativity(),\n            sql_ast::Expr::UnaryOp { op, .. } => op.associativity(),\n            _ => Associativity::Both,\n        }\n    }\n}\n\nimpl SQLExpression for BinaryOperator {\n    fn binding_strength(&self) -> i32 {\n        use BinaryOperator::*;\n        match self {\n            Modulo | Multiply | Divide => 11,\n            Minus | Plus => 10,\n\n            Gt | Lt | GtEq | LtEq | Eq | NotEq => 6,\n\n            And => 3,\n            Or => 2,\n\n            _ => 9,\n        }\n    }\n    fn associativity(&self) -> Associativity {\n        use BinaryOperator::*;\n        match self {\n            Minus | Divide | Modulo => Associativity::Left,\n            _ => Associativity::Both,\n        }\n    }\n}\nimpl SQLExpression for UnaryOperator {\n    fn binding_strength(&self) -> i32 {\n        match self {\n            UnaryOperator::Minus | UnaryOperator::Plus => 13,\n            UnaryOperator::Not => 4,\n            _ => 9,\n        }\n    }\n}\n\n/// A wrapper around sql_ast::Expr, that may have already been converted to source.\n#[derive(Debug, Clone)]\npub enum ExprOrSource {\n    Expr(Box<sql_ast::Expr>),\n    Source(SourceExpr),\n}\n\n#[derive(Debug, Clone)]\npub struct SourceExpr {\n    pub text: String,\n    pub binding_strength: i32,\n\n    /// True for expressions that support (and need) the window frame (OVER ...)\n    pub window_frame: bool,\n}\n\nimpl ExprOrSource {\n    pub fn into_ast(self) -> sql_ast::Expr {\n        match self {\n            ExprOrSource::Expr(ast) => *ast,\n            ExprOrSource::Source(SourceExpr { text: source, .. }) => {\n                // The s-string hack\n                sql_ast::Expr::Identifier(sql_ast::Ident::new(source))\n            }\n        }\n    }\n\n    pub fn into_source(self) -> String {\n        match self {\n            ExprOrSource::Expr(e) => e.to_string(),\n            ExprOrSource::Source(SourceExpr { text, .. }) => text,\n        }\n    }\n\n    fn wrap_in_parenthesis(self) -> Self {\n        match self {\n            ExprOrSource::Expr(expr) => ExprOrSource::Expr(Box::new(sql_ast::Expr::Nested(expr))),\n            ExprOrSource::Source(SourceExpr {\n                text, window_frame, ..\n            }) => {\n                let text = format!(\"({text})\");\n                ExprOrSource::Source(SourceExpr {\n                    text,\n                    binding_strength: 100,\n                    window_frame,\n                })\n            }\n        }\n    }\n}\n\nimpl SQLExpression for ExprOrSource {\n    fn binding_strength(&self) -> i32 {\n        match self {\n            ExprOrSource::Expr(expr) => expr.binding_strength(),\n            ExprOrSource::Source(SourceExpr {\n                binding_strength, ..\n            }) => *binding_strength,\n        }\n    }\n}\n\nimpl From<sql_ast::Expr> for ExprOrSource {\n    fn from(value: sql_ast::Expr) -> Self {\n        ExprOrSource::Expr(Box::new(value))\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use insta::assert_yaml_snapshot;\n\n    use super::*;\n\n    #[test]\n    fn test_range_of_ranges() -> Result<()> {\n        fn from_ints(start: Option<i64>, end: Option<i64>) -> Range<rq::Expr> {\n            let start = start.map(|x| rq::Expr {\n                kind: rq::ExprKind::Literal(Literal::Integer(x)),\n                span: None,\n            });\n            let end = end.map(|x| rq::Expr {\n                kind: rq::ExprKind::Literal(Literal::Integer(x)),\n                span: None,\n            });\n            Range { start, end }\n        }\n\n        let range_1_10 = from_ints(Some(1), Some(10));\n        let range_5_6 = from_ints(Some(5), Some(6));\n        let range_5_inf = from_ints(Some(5), None);\n        let range_inf_8 = from_ints(None, Some(8));\n        let range_5_5 = from_ints(Some(5), Some(5));\n\n        assert!(range_of_ranges(vec![range_1_10.clone()])?.end.is_some());\n\n        assert_yaml_snapshot!(range_of_ranges(vec![range_1_10.clone()])?, @r\"\n        start: 1\n        end: 10\n        \");\n\n        assert_yaml_snapshot!(range_of_ranges(vec![range_1_10.clone(), range_1_10.clone()])?, @r\"\n        start: 1\n        end: 10\n        \");\n\n        assert_yaml_snapshot!(range_of_ranges(vec![range_1_10.clone(), range_5_6.clone()])?, @r\"\n        start: 5\n        end: 6\n        \");\n\n        assert_yaml_snapshot!(range_of_ranges(vec![range_5_6.clone(), range_1_10.clone()])?, @r\"\n        start: 5\n        end: 6\n        \");\n\n        // empty range\n        assert_yaml_snapshot!(range_of_ranges(vec![range_5_6.clone(), range_5_6.clone()])?, @r\"\n        start: ~\n        end: 0\n        \");\n\n        assert_yaml_snapshot!(range_of_ranges(vec![range_5_inf.clone(), range_5_inf.clone()])?, @r\"\n        start: 9\n        end: ~\n        \");\n\n        assert_yaml_snapshot!(range_of_ranges(vec![range_1_10, range_5_inf])?, @r\"\n        start: 5\n        end: 10\n        \");\n\n        assert_yaml_snapshot!(range_of_ranges(vec![range_5_6, range_inf_8.clone()])?, @r\"\n        start: 5\n        end: 6\n        \");\n\n        assert_yaml_snapshot!(range_of_ranges(vec![range_inf_8.clone(), range_inf_8])?, @r\"\n        start: ~\n        end: 8\n        \");\n\n        assert_yaml_snapshot!(range_of_ranges(vec![range_5_5])?, @r\"\n        start: 5\n        end: 5\n        \");\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/gen_projection.rs",
    "content": "use std::collections::{HashMap, HashSet};\n\nuse itertools::Itertools;\nuse sqlparser::ast::{\n    self as sql_ast, ExceptSelectItem, ExcludeSelectItem, ObjectName, SelectItem,\n    WildcardAdditionalOptions,\n};\n\nuse super::dialect::ColumnExclude;\nuse super::gen_expr::*;\nuse super::pq::context::{AnchorContext, ColumnDecl};\nuse super::Context;\nuse crate::ir::pl::Ident;\nuse crate::ir::rq::{CId, RelationColumn};\nuse crate::Result;\nuse crate::{Error, Span, WithErrorInfo};\n\npub(super) fn try_into_exprs(\n    cids: Vec<CId>,\n    ctx: &mut Context,\n    span: Option<Span>,\n) -> Result<Vec<sql_ast::Expr>> {\n    let (cids, excluded) = translate_wildcards(&ctx.anchor, cids);\n\n    let mut res = Vec::new();\n    for cid in cids {\n        let decl = ctx.anchor.column_decls.get(&cid).unwrap();\n\n        let ColumnDecl::RelationColumn(riid, _, RelationColumn::Wildcard) = decl else {\n            // base case\n            res.push(translate_cid(cid, ctx)?.into_ast());\n            continue;\n        };\n\n        // star\n        let t = &ctx.anchor.relation_instances[riid];\n        let table_name = t.table_ref.name.clone().map(Ident::from_name);\n\n        let ident = translate_star(ctx, span)?;\n        if let Some(excluded) = excluded.get(&cid) {\n            if !excluded.is_empty() {\n                return Err(\n                    Error::new_simple(\"Excluding columns not supported as this position\")\n                        .with_span(span),\n                );\n            }\n        }\n        let ident = translate_ident(table_name, Some(ident), ctx);\n\n        res.push(sql_ast::Expr::CompoundIdentifier(ident));\n    }\n    Ok(res)\n}\n\ntype Excluded = HashMap<CId, HashSet<CId>>;\n\n/// Convert RQ wildcards to SQL stars.\n/// Note that they don't have the same semantics:\n/// - wildcard means \"other columns that we don't have the knowledge of\"\n/// - star means \"all columns of the table\"\n///\npub(super) fn translate_wildcards(ctx: &AnchorContext, cols: Vec<CId>) -> (Vec<CId>, Excluded) {\n    let mut star = None;\n    let mut excluded: Excluded = HashMap::new();\n\n    // When compiling:\n    // from employees | group department (take 3)\n    // Row number will be computed in a CTE that also contains a star.\n    // In the main query, star will also include row number, which was not\n    // requested.\n    // This function adds that column to the exclusion tuple.\n    fn exclude(star: &mut Option<(CId, HashSet<CId>)>, excluded: &mut Excluded) {\n        let Some((cid, in_star)) = star.take() else {\n            return;\n        };\n        if in_star.is_empty() {\n            return;\n        }\n\n        excluded.insert(cid, in_star);\n    }\n\n    let mut output = Vec::new();\n    for cid in cols {\n        // don't use cols that have been included by preceding star\n        let in_star = star\n            .as_mut()\n            .map(|s: &mut (CId, HashSet<CId>)| s.1.remove(&cid))\n            .unwrap_or_default();\n        if in_star {\n            continue;\n        }\n\n        if let ColumnDecl::RelationColumn(riid, _, col) = &ctx.column_decls[&cid] {\n            if matches!(col, RelationColumn::Wildcard) {\n                exclude(&mut star, &mut excluded);\n\n                let relation_instance = &ctx.relation_instances[riid];\n                let mut in_star: HashSet<_> =\n                    relation_instance.original_cids.iter().cloned().collect();\n                in_star.remove(&cid);\n                star = Some((cid, in_star));\n\n                // remove preceding cols that will be included with this star\n                if let Some((_, in_star)) = &mut star {\n                    while let Some(prev) = output.pop() {\n                        if !in_star.remove(&prev) {\n                            output.push(prev);\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n        output.push(cid);\n    }\n\n    exclude(&mut star, &mut excluded);\n    (output, excluded)\n}\n\nfn deduplicate_select_items(items: &mut Vec<SelectItem>) {\n    // Dropping all duplicated identifiers\n    let mut seen = HashSet::new();\n    items.retain(|select_item| match select_item {\n        SelectItem::UnnamedExpr(sql_ast::Expr::CompoundIdentifier(idents)) => {\n            // If any of the identifiers hadn't been seen yet, retain the expr\n            idents.iter().any(|ident| seen.insert(ident.clone()))\n        }\n        SelectItem::ExprWithAlias { alias, .. } => seen.insert(alias.clone()),\n        _ => true,\n    });\n}\n\npub(super) fn translate_select_items(\n    cols: Vec<CId>,\n    mut excluded: Excluded,\n    ctx: &mut Context,\n) -> Result<Vec<SelectItem>> {\n    let mut res: Vec<_> = cols\n        .into_iter()\n        .map(|cid| {\n            let decl = ctx.anchor.column_decls.get(&cid).unwrap();\n\n            let ColumnDecl::RelationColumn(riid, _, RelationColumn::Wildcard) = decl else {\n                // general case\n                return translate_select_item(cid, ctx);\n            };\n\n            // wildcard case\n            let t = &ctx.anchor.relation_instances[riid];\n            let table_name = t.table_ref.name.clone().map(Ident::from_name);\n\n            let ident = translate_ident(table_name, Some(\"*\".to_string()), ctx);\n\n            // excluded columns\n            let opts = (excluded.remove(&cid))\n                .and_then(|excluded| translate_exclude(ctx, excluded))\n                .unwrap_or_default();\n\n            Ok(if ident.len() > 1 {\n                let mut object_name = ident;\n                object_name.pop();\n                SelectItem::QualifiedWildcard(\n                    sqlparser::ast::SelectItemQualifiedWildcardKind::ObjectName(ObjectName(\n                        object_name\n                            .into_iter()\n                            .map(sqlparser::ast::ObjectNamePart::Identifier)\n                            .collect(),\n                    )),\n                    opts,\n                )\n            } else {\n                SelectItem::Wildcard(opts)\n            })\n        })\n        .try_collect()?;\n\n    deduplicate_select_items(&mut res);\n\n    if res.is_empty() && !ctx.dialect.supports_zero_columns() {\n        // In some cases, no columns will appear in the projection\n        // for SQL to parse correctly, we inject a `NULL`.\n        // This is not strictly correct and should probably generate an error\n        // instead.\n        // Example: `from x | take 10 | aggregate { count this }`.\n        // Here, first SELECT does not need to emit any columns as we don't need\n        // any since we just count the number of rows.\n        res.push(SelectItem::UnnamedExpr(sql_ast::Expr::Value(\n            sql_ast::Value::Null.into(),\n        )));\n    }\n    Ok(res)\n}\n\nfn translate_exclude(\n    ctx: &mut Context,\n    excluded: HashSet<CId>,\n) -> Option<WildcardAdditionalOptions> {\n    let excluded = as_col_names(&excluded, &ctx.anchor);\n\n    let Some(supported) = ctx.dialect.column_exclude() else {\n        // TODO: eventually this should throw an error\n        //   I don't want to do this now, because we have no way around it.\n        //   We could also ask the user to add table definitions.\n        if log::log_enabled!(log::Level::Warn) {\n            let excluded = excluded.join(\", \");\n\n            log::warn!(\"Columns {excluded} will be included with *, but were not requested.\")\n        }\n        return None;\n    };\n\n    let mut excluded = excluded\n        .into_iter()\n        .map(|name| translate_ident_part(name.to_string(), ctx))\n        .collect_vec();\n\n    Some(match supported {\n        ColumnExclude::Exclude => WildcardAdditionalOptions {\n            opt_exclude: Some(ExcludeSelectItem::Multiple(excluded)),\n            ..Default::default()\n        },\n        ColumnExclude::Except => WildcardAdditionalOptions {\n            opt_except: Some(ExceptSelectItem {\n                first_element: excluded.remove(0),\n                additional_elements: excluded,\n            }),\n            ..Default::default()\n        },\n    })\n}\n\nfn as_col_names<'a>(cids: &'a HashSet<CId>, ctx: &'a AnchorContext) -> Vec<&'a str> {\n    cids.iter()\n        .sorted_by_key(|c| c.get())\n        .map(|c| {\n            ctx.column_decls\n                .get(c)\n                .and_then(|c| match c {\n                    ColumnDecl::RelationColumn(_, _, rc) => rc.as_single().map(|o| o.as_ref()),\n                    _ => None,\n                })\n                .flatten()\n                .map(|n| n.as_str())\n                .unwrap_or(\"<unnamed>\")\n        })\n        .collect_vec()\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/gen_query.rs",
    "content": "//! This module is responsible for translating PRQL AST to sqlparser AST, and\n//! then to a String. We use sqlparser because it's trivial to create the string\n//! once it's in their AST (it's just `.to_string()`). It also lets us support a\n//! few dialects of SQL immediately.\nuse itertools::Itertools;\nuse regex::Regex;\nuse sqlparser::ast::{\n    self as sql_ast, Join, JoinConstraint, JoinOperator, Select, SelectItem, SetExpr, TableAlias,\n    TableFactor, TableWithJoins,\n};\n\nuse super::gen_expr::*;\nuse super::gen_projection::*;\nuse super::operators::translate_operator;\nuse super::pq::ast::{Cte, CteKind, RelationExpr, RelationExprKind, SqlRelation, SqlTransform};\nuse super::{Context, Dialect};\nuse crate::debug;\nuse crate::ir::pl::{JoinSide, Literal};\nuse crate::ir::rq::{CId, Expr, ExprKind, RelationLiteral, RelationalQuery};\nuse crate::utils::{BreakUp, Pluck};\nuse crate::{Error, Result, WithErrorInfo};\nuse prqlc_parser::generic::InterpolateItem;\n\ntype Transform = SqlTransform<RelationExpr, ()>;\n\npub fn translate_query(query: RelationalQuery, dialect: Option<Dialect>) -> Result<sql_ast::Query> {\n    // compile from RQ to PQ\n    let (pq_query, mut ctx) = super::pq::compile_query(query, dialect)?;\n\n    debug::log_stage(debug::Stage::Sql(debug::StageSql::Main));\n    let mut query = translate_relation(pq_query.main_relation, &mut ctx)?;\n\n    if !pq_query.ctes.is_empty() {\n        // attach CTEs\n        let mut cte_tables = Vec::new();\n        let mut recursive = false;\n        for cte in pq_query.ctes {\n            let (cte, rec) = translate_cte(cte, &mut ctx)?;\n            cte_tables.push(cte);\n            recursive = recursive || rec;\n        }\n        query.with = Some(sql_ast::With {\n            recursive,\n            cte_tables,\n            with_token: sqlparser::ast::helpers::attached_token::AttachedToken::empty(),\n        });\n    }\n\n    debug::log_entry(|| debug::DebugEntryKind::ReprSqlParser(Box::new(query.clone())));\n    Ok(query)\n}\n\nfn translate_relation(relation: SqlRelation, ctx: &mut Context) -> Result<sql_ast::Query> {\n    match relation {\n        SqlRelation::AtomicPipeline(pipeline) => translate_pipeline(pipeline, ctx),\n        SqlRelation::Literal(data) => translate_relation_literal(data, ctx),\n        SqlRelation::SString(items) => translate_query_sstring(items, ctx),\n        SqlRelation::Operator { name, args } => translate_query_operator(name, args, ctx),\n    }\n}\n\nfn translate_pipeline(pipeline: Vec<Transform>, ctx: &mut Context) -> Result<sql_ast::Query> {\n    use SqlTransform::*;\n\n    let (select, set_ops) =\n        pipeline.break_up(|t| matches!(t, Union { .. } | Except { .. } | Intersect { .. }));\n\n    let select = translate_select_pipeline(select, ctx)?;\n\n    translate_set_ops_pipeline(select, set_ops, ctx)\n}\n\nfn translate_select_pipeline(\n    mut pipeline: Vec<Transform>,\n    ctx: &mut Context,\n) -> Result<sql_ast::Query> {\n    let table_count = count_tables(&pipeline);\n    log::debug!(\"atomic query contains {table_count} tables\");\n    ctx.push_query();\n    ctx.query.omit_ident_prefix = table_count == 1;\n    ctx.query.pre_projection = true;\n\n    let mut from: Vec<_> = pipeline\n        .pluck(|t| t.into_from())\n        .into_iter()\n        .map(|source| -> Result<TableWithJoins> {\n            Ok(TableWithJoins {\n                relation: translate_relation_expr(source, ctx)?,\n                joins: vec![],\n            })\n        })\n        .try_collect()?;\n\n    let joins = pipeline\n        .pluck(|t| t.into_join())\n        .into_iter()\n        .map(|j| translate_join(j, ctx))\n        .collect::<Result<Vec<_>>>()?;\n    if !joins.is_empty() {\n        if let Some(from) = from.last_mut() {\n            from.joins = joins;\n        } else {\n            unreachable!()\n        }\n    }\n\n    let projection = pipeline\n        .pluck(|t| t.into_select())\n        .into_iter()\n        .exactly_one()\n        .unwrap();\n    let projection = translate_wildcards(&ctx.anchor, projection);\n    let mut projection = translate_select_items(projection.0, projection.1, ctx)?;\n\n    let order_by = pipeline.pluck(|t| t.into_sort());\n    let takes = pipeline.pluck(|t| t.into_take());\n    let is_distinct = pipeline.iter().any(|t| matches!(t, SqlTransform::Distinct));\n    let distinct_ons = pipeline.pluck(|t| t.into_distinct_on());\n    let distinct = if is_distinct {\n        Some(sql_ast::Distinct::Distinct)\n    } else if !distinct_ons.is_empty() {\n        Some(sql_ast::Distinct::On(\n            distinct_ons\n                .into_iter()\n                .exactly_one()\n                .unwrap()\n                .into_iter()\n                .map(|id| translate_cid(id, ctx).map(|x| x.into_ast()))\n                .collect::<Result<Vec<_>>>()?,\n        ))\n    } else {\n        None\n    };\n\n    // When we have DISTINCT ON, we must have at least a wildcard in the projection\n    // (PostgreSQL requires DISTINCT ON to have a non-empty SELECT list)\n    // Replace NULL placeholder with wildcard if present, or add wildcard if empty\n    if matches!(distinct, Some(sql_ast::Distinct::On(_))) {\n        if projection.len() == 1 {\n            if let SelectItem::UnnamedExpr(sql_ast::Expr::Value(ref v)) = projection[0] {\n                if matches!(v.value, sql_ast::Value::Null) {\n                    projection[0] =\n                        SelectItem::Wildcard(sql_ast::WildcardAdditionalOptions::default());\n                }\n            }\n        } else if projection.is_empty() {\n            projection.push(SelectItem::Wildcard(\n                sql_ast::WildcardAdditionalOptions::default(),\n            ));\n        }\n    }\n\n    // Split the pipeline into before & after the aggregate\n    let (mut before_agg, mut after_agg) =\n        pipeline.break_up(|t| matches!(t, Transform::Aggregate { .. } | Transform::Union { .. }));\n\n    // WHERE and HAVING\n    let where_ = filter_of_conditions(before_agg.pluck(|t| t.into_filter()), ctx)?;\n    let having = filter_of_conditions(after_agg.pluck(|t| t.into_filter()), ctx)?;\n\n    // GROUP BY\n    let aggregate = after_agg.pluck(|t| t.into_aggregate()).into_iter().next();\n    let group_by: Vec<CId> = aggregate.map(|(part, _)| part).unwrap_or_default();\n    ctx.query.allow_stars = ctx.dialect.stars_in_group();\n    let group_by = sql_ast::GroupByExpr::Expressions(try_into_exprs(group_by, ctx, None)?, vec![]);\n    ctx.query.allow_stars = true;\n\n    ctx.query.pre_projection = false;\n\n    let ranges = takes.into_iter().map(|x| x.range).collect();\n    let take = range_of_ranges(ranges)?;\n    let offset = take.start.map(|s| s - 1).unwrap_or(0);\n    let limit = take.end.map(|e| e - offset);\n\n    let mut offset = if offset == 0 {\n        None\n    } else {\n        let kind = ExprKind::Literal(Literal::Integer(offset));\n        let expr = Expr { kind, span: None };\n        Some(sqlparser::ast::Offset {\n            value: translate_expr(expr, ctx)?.into_ast(),\n            rows: if ctx.dialect.use_fetch() {\n                sqlparser::ast::OffsetRows::Rows\n            } else {\n                sqlparser::ast::OffsetRows::None\n            },\n        })\n    };\n\n    // Use sorting from the frame\n    let mut order_by: Vec<sql_ast::OrderByExpr> = order_by\n        .last()\n        .map(|sorts| {\n            sorts\n                .iter()\n                .map(|s| translate_column_sort(s, ctx))\n                .try_collect()\n        })\n        .transpose()?\n        .unwrap_or_default();\n\n    let (fetch, limit) = if ctx.dialect.use_fetch() {\n        (limit.map(|l| fetch_of_i64(l, ctx)), None)\n    } else {\n        (None, limit.map(expr_of_i64))\n    };\n\n    // If we have a FETCH we need to make sure that:\n    // - we have an OFFSET (set to 0)\n    // - we have an ORDER BY (see https://stackoverflow.com/a/44919325)\n    if fetch.is_some() {\n        if offset.is_none() {\n            let kind = ExprKind::Literal(Literal::Integer(0));\n            let expr = Expr { kind, span: None };\n            offset = Some(sqlparser::ast::Offset {\n                value: translate_expr(expr, ctx)?.into_ast(),\n                rows: sqlparser::ast::OffsetRows::Rows,\n            })\n        }\n        if order_by.is_empty() {\n            // When DISTINCT is used, MSSQL requires ORDER BY items to appear\n            // in the SELECT list. Use the first column from the projection\n            // instead of (SELECT NULL).\n            let order_expr = is_distinct\n                .then(|| first_expr_from_projection(&projection))\n                .flatten()\n                .unwrap_or_else(|| {\n                    sql_ast::Expr::Value(\n                        sql_ast::Value::Placeholder(\"(SELECT NULL)\".to_string()).into(),\n                    )\n                });\n            order_by.push(sql_ast::OrderByExpr {\n                expr: order_expr,\n                options: sqlparser::ast::OrderByOptions {\n                    asc: None,\n                    nulls_first: None,\n                },\n                with_fill: None,\n            });\n        }\n    }\n\n    ctx.pop_query();\n\n    Ok(sql_ast::Query {\n        order_by: if order_by.is_empty() {\n            None\n        } else {\n            Some(sql_ast::OrderBy {\n                kind: sqlparser::ast::OrderByKind::Expressions(order_by),\n                interpolate: None,\n            })\n        },\n        limit_clause: if limit.is_some() || offset.is_some() {\n            Some(sql_ast::LimitClause::LimitOffset {\n                limit,\n                offset,\n                limit_by: Vec::new(),\n            })\n        } else {\n            None\n        },\n        fetch,\n        ..default_query(SetExpr::Select(Box::new(Select {\n            distinct,\n            projection,\n            from,\n            selection: where_,\n            group_by,\n            having,\n            ..default_select()\n        })))\n    })\n}\n\nfn translate_set_ops_pipeline(\n    mut top: sql_ast::Query,\n    mut pipeline: Vec<Transform>,\n    context: &mut Context,\n) -> Result<sql_ast::Query> {\n    // reverse, so it's easier (and O(1)) to pop\n    pipeline.reverse();\n\n    while let Some(transform) = pipeline.pop() {\n        use SqlTransform::*;\n\n        let op = match &transform {\n            Union { .. } => sql_ast::SetOperator::Union,\n            Except { .. } => sql_ast::SetOperator::Except,\n            Intersect { .. } => sql_ast::SetOperator::Intersect,\n            Sort(_) => continue,\n            _ => unreachable!(),\n        };\n\n        let (distinct, bottom) = match transform {\n            Union { distinct, bottom }\n            | Except { distinct, bottom }\n            | Intersect { distinct, bottom } => (distinct, bottom),\n            _ => unreachable!(),\n        };\n\n        // Some engines (like SQLite) do not support subqueries between simple parentheses, so we keep\n        // the general `SELECT * FROM (query)` or `SELECT * FROM cte` by default for complex cases.\n        //\n        // Some engines (like Postgres) need the subquery directly to properly match column type on\n        // both sides. For those:\n        // 1. we need the subquery as-is or in parentheses\n        // 2. we need to avoid CTEs\n\n        // Left hand side (aka top)\n        let left = query_to_set_expr(top, context);\n\n        // Right hand side (aka bottom)\n        let right_rel = translate_relation_expr(bottom, context)?;\n        let right = if let TableFactor::Derived { subquery, .. } = right_rel {\n            query_to_set_expr(*subquery, context)\n        } else {\n            Box::new(SetExpr::Select(Box::new(sql_ast::Select {\n                projection: vec![SelectItem::Wildcard(\n                    sql_ast::WildcardAdditionalOptions::default(),\n                )],\n                from: vec![TableWithJoins {\n                    relation: right_rel,\n                    joins: vec![],\n                }],\n                ..default_select()\n            })))\n        };\n\n        top = default_query(SetExpr::SetOperation {\n            left,\n            right,\n            set_quantifier: if distinct {\n                if context.dialect.set_ops_distinct() {\n                    sql_ast::SetQuantifier::Distinct\n                } else {\n                    sql_ast::SetQuantifier::None\n                }\n            } else {\n                sql_ast::SetQuantifier::All\n            },\n            op,\n        });\n    }\n\n    Ok(top)\n}\n\nfn translate_relation_expr(relation_expr: RelationExpr, ctx: &mut Context) -> Result<TableFactor> {\n    let alias = Some(&relation_expr.riid)\n        .and_then(|riid| ctx.anchor.relation_instances.get(riid))\n        .and_then(|ri| ri.table_ref.name.clone());\n\n    Ok(match relation_expr.kind {\n        RelationExprKind::Ref(tid) => {\n            let decl = ctx.anchor.lookup_table_decl(&tid).unwrap();\n\n            // prepare names\n            let table_name = decl.name.clone().unwrap();\n\n            let name = sql_ast::ObjectName(\n                translate_ident(Some(table_name.clone()), None, ctx)\n                    .into_iter()\n                    .map(sqlparser::ast::ObjectNamePart::Identifier)\n                    .collect(),\n            );\n\n            TableFactor::Table {\n                name,\n                alias: if Some(table_name.name) == alias {\n                    None\n                } else {\n                    translate_table_alias(alias, ctx)\n                },\n                args: None,\n                with_hints: vec![],\n                with_ordinality: false,\n                version: None,\n                partitions: vec![],\n                json_path: None,\n                sample: None,\n                index_hints: vec![],\n            }\n        }\n        RelationExprKind::SubQuery(query) => {\n            let query = translate_relation(query, ctx)?;\n\n            let alias = translate_table_alias(alias, ctx);\n\n            TableFactor::Derived {\n                lateral: false,\n                subquery: Box::new(query),\n                alias,\n            }\n        }\n    })\n}\n\nfn translate_table_alias(alias: Option<String>, ctx: &mut Context) -> Option<TableAlias> {\n    alias\n        .map(|ident| translate_ident_part(ident, ctx))\n        .map(simple_table_alias)\n}\n\nfn translate_join(\n    (side, with, filter): (JoinSide, RelationExpr, Expr),\n    ctx: &mut Context,\n) -> Result<Join> {\n    let relation = translate_relation_expr(with, ctx)?;\n\n    let constraint = JoinConstraint::On(translate_expr(filter, ctx)?.into_ast());\n\n    Ok(Join {\n        relation,\n        join_operator: match side {\n            JoinSide::Inner => JoinOperator::Inner(constraint),\n            JoinSide::Left => JoinOperator::LeftOuter(constraint),\n            JoinSide::Right => JoinOperator::RightOuter(constraint),\n            JoinSide::Full => JoinOperator::FullOuter(constraint),\n        },\n        global: false,\n    })\n}\n\nfn translate_cte(cte: Cte, ctx: &mut Context) -> Result<(sql_ast::Cte, bool)> {\n    let decl = ctx.anchor.lookup_table_decl(&cte.tid).unwrap();\n    let cte_name = decl.name.clone().unwrap();\n\n    let cte_name = translate_ident(Some(cte_name), None, ctx).pop().unwrap();\n\n    let (query, recursive) = match cte.kind {\n        // base case\n        CteKind::Normal(rel) => (translate_relation(rel, ctx)?, false),\n\n        // special: WITH RECURSIVE\n        CteKind::Loop { initial, step } => {\n            // compile initial\n            let initial = query_to_set_expr(translate_relation(initial, ctx)?, ctx);\n\n            let step = query_to_set_expr(translate_relation(step, ctx)?, ctx);\n\n            // build CTE and its SELECT\n            let inner_query = default_query(SetExpr::SetOperation {\n                op: sql_ast::SetOperator::Union,\n                set_quantifier: sql_ast::SetQuantifier::All,\n                left: initial,\n                right: step,\n            });\n\n            (inner_query, true)\n\n            // RECURSIVE can only follow WITH directly.\n            // Initial implementation assumed that it applies only to the first CTE.\n            // This meant that it had to wrap any-non-first CTE into a *nested* WITH, so the inner\n            // WITH could be RECURSIVE.\n            // This is implementation of that, in case some dialect requires it.\n            // let inner_cte = sql_ast::Cte {\n            //     alias: simple_table_alias(cte_name.clone()),\n            //     query: Box::new(inner_query),\n            //     from: None,\n            // };\n            // let outer_query = sql_ast::Query {\n            //     with: Some(sql_ast::With {\n            //         recursive: true,\n            //         cte_tables: vec![inner_cte],\n            //     }),\n            //     ..default_query(sql_ast::SetExpr::Select(Box::new(sql_ast::Select {\n            //         projection: vec![SelectItem::Wildcard(\n            //             sql_ast::WildcardAdditionalOptions::default(),\n            //         )],\n            //         from: vec![TableWithJoins {\n            //             relation: TableFactor::Table {\n            //                 name: sql_ast::ObjectName(vec![cte_name.clone()]),\n            //                 alias: None,\n            //                 args: None,\n            //                 with_hints: Vec::new(),\n            //             },\n            //             joins: vec![],\n            //         }],\n            //         ..default_select()\n            //     })))\n            // };\n            // (outer_query, false)\n        }\n    };\n\n    let cte = sql_ast::Cte {\n        alias: cte_table_alias(cte_name),\n        query: Box::new(query),\n        from: None,\n        materialized: None,\n        closing_paren_token: sqlparser::ast::helpers::attached_token::AttachedToken::empty(),\n    };\n    Ok((cte, recursive))\n}\n\nfn translate_relation_literal(data: RelationLiteral, ctx: &Context) -> Result<sql_ast::Query> {\n    // TODO: this could be made to use VALUES instead of SELECT UNION ALL SELECT\n    //       I'm not sure about compatibility though.\n    // edit: probably not, because VALUES has no way of setting names of the columns\n    //       Postgres will just name them column1, column2 as so on.\n    //       Which means we can use VALUES, but only if this is not the top-level statement,\n    //       where they really matter.\n\n    if data.rows.is_empty() {\n        let mut nulls: Vec<_> = (data.columns.iter())\n            .map(|col_name| SelectItem::ExprWithAlias {\n                expr: sql_ast::Expr::Value(sql_ast::Value::Null.into()),\n                alias: translate_ident_part(col_name.clone(), ctx),\n            })\n            .collect();\n\n        // empty projection is a parse error in some dialects, let's inject a NULL\n        if nulls.is_empty() {\n            nulls.push(SelectItem::UnnamedExpr(sql_ast::Expr::Value(\n                sql_ast::Value::Null.into(),\n            )));\n        }\n\n        return Ok(default_query(sql_ast::SetExpr::Select(Box::new(Select {\n            projection: nulls,\n            selection: Some(sql_ast::Expr::Value(sql_ast::Value::Boolean(false).into())),\n            ..default_select()\n        }))));\n    }\n\n    let mut selects = Vec::with_capacity(data.rows.len());\n\n    for row in data.rows {\n        let body = sql_ast::SetExpr::Select(Box::new(Select {\n            projection: std::iter::zip(data.columns.clone(), row)\n                .map(|(col, value)| -> Result<_> {\n                    Ok(SelectItem::ExprWithAlias {\n                        expr: translate_literal(value, ctx)?,\n                        alias: translate_ident_part(col, ctx),\n                    })\n                })\n                .try_collect()?,\n            ..default_select()\n        }));\n\n        selects.push(body)\n    }\n\n    let mut body = selects.remove(0);\n    for select in selects {\n        body = SetExpr::SetOperation {\n            op: sql_ast::SetOperator::Union,\n            set_quantifier: sql_ast::SetQuantifier::All,\n            left: Box::new(body),\n            right: Box::new(select),\n        }\n    }\n\n    Ok(default_query(body))\n}\n\npub(super) fn translate_query_sstring(\n    items: Vec<InterpolateItem<Expr>>,\n    ctx: &mut Context,\n) -> Result<sql_ast::Query> {\n    let string = translate_sstring(items, ctx)?;\n\n    let re = Regex::new(r\"(?i)^SELECT\\b\").unwrap();\n    let prefix = string.trim().get(0..7).unwrap_or_default();\n\n    if re.is_match(prefix) {\n        if let Some(string) = string.trim().strip_prefix(prefix) {\n            return Ok(default_query(sql_ast::SetExpr::Select(Box::new(\n                sql_ast::Select {\n                    projection: vec![sql_ast::SelectItem::UnnamedExpr(sql_ast::Expr::Identifier(\n                        sql_ast::Ident::new(string),\n                    ))],\n                    ..default_select()\n                },\n            ))));\n        }\n    }\n\n    Err(\n        Error::new_simple(\"s-strings representing a table must start with `SELECT `\".to_string())\n            .push_hint(\"this is a limitation by current compiler implementation\"),\n    )\n}\n\npub(super) fn translate_query_operator(\n    name: String,\n    args: Vec<Expr>,\n    ctx: &mut Context,\n) -> Result<sql_ast::Query> {\n    let from_s_string = translate_operator(name, args, ctx)?;\n\n    let s_string = format!(\" * FROM {}\", from_s_string.text);\n\n    Ok(default_query(sql_ast::SetExpr::Select(Box::new(\n        sql_ast::Select {\n            projection: vec![sql_ast::SelectItem::UnnamedExpr(sql_ast::Expr::Identifier(\n                sql_ast::Ident::new(s_string),\n            ))],\n            ..default_select()\n        },\n    ))))\n}\n\nfn filter_of_conditions(exprs: Vec<Expr>, context: &mut Context) -> Result<Option<sql_ast::Expr>> {\n    Ok(if let Some(cond) = all(exprs) {\n        Some(translate_expr(cond, context)?.into_ast())\n    } else {\n        None\n    })\n}\n\nfn all(mut exprs: Vec<Expr>) -> Option<Expr> {\n    let mut condition = exprs.pop()?;\n    while let Some(expr) = exprs.pop() {\n        condition = Expr {\n            kind: ExprKind::Operator {\n                name: \"std.and\".to_string(),\n                args: vec![expr, condition],\n            },\n            span: None,\n        };\n    }\n    Some(condition)\n}\n\nfn default_query(body: sql_ast::SetExpr) -> sql_ast::Query {\n    sql_ast::Query {\n        with: None,\n        body: Box::new(body),\n        order_by: None,\n        limit_clause: None,\n        fetch: None,\n        locks: Vec::new(),\n        for_clause: None,\n        settings: None,\n        format_clause: None,\n        pipe_operators: Vec::new(),\n    }\n}\n\nfn default_select() -> Select {\n    Select {\n        distinct: None,\n        top: None,\n        top_before_distinct: false,\n        projection: Vec::new(),\n        into: None,\n        from: Vec::new(),\n        lateral_views: Vec::new(),\n        selection: None,\n        group_by: sql_ast::GroupByExpr::Expressions(vec![], vec![]),\n        cluster_by: Vec::new(),\n        distribute_by: Vec::new(),\n        sort_by: Vec::new(),\n        having: None,\n        named_window: vec![],\n        qualify: None,\n        value_table_mode: None,\n        window_before_qualify: false,\n        connect_by: None,\n        prewhere: None,\n        exclude: None,\n        select_token: sqlparser::ast::helpers::attached_token::AttachedToken::empty(),\n        flavor: sqlparser::ast::SelectFlavor::Standard,\n    }\n}\n\nfn simple_table_alias(name: sql_ast::Ident) -> TableAlias {\n    TableAlias {\n        name,\n        columns: Vec::new(),\n        explicit: true,\n    }\n}\n\nfn cte_table_alias(name: sql_ast::Ident) -> TableAlias {\n    TableAlias {\n        name,\n        columns: Vec::new(),\n        explicit: false,\n    }\n}\n\nfn query_to_set_expr(query: sql_ast::Query, context: &mut Context) -> Box<SetExpr> {\n    let is_simple = query.with.is_none()\n        && query.order_by.is_none()\n        && query.limit_clause.is_none()\n        && query.fetch.is_none()\n        && query.locks.is_empty();\n\n    if is_simple {\n        return query.body;\n    }\n\n    // Query is not simple, so we need to wrap it.\n    //\n    // Some engines (like SQLite) do not support subqueries between simple parentheses, so we keep\n    // the general `SELECT * FROM (query)` by default.\n    //\n    // Some engines (like Postgres) may need the subquery directly as-is or between parentheses\n    // to properly match columns for syntaxes like `UNION`, `EXCEPT`, and `INTERSECT`.\n    // Incidentally, the parenthesis syntax `(query)` allows for complex queries.\n    let set_expr = if context.dialect.prefers_subquery_parentheses_shorthand() {\n        SetExpr::Query(query.into())\n    } else {\n        SetExpr::Select(Box::new(Select {\n            projection: vec![SelectItem::Wildcard(\n                sql_ast::WildcardAdditionalOptions::default(),\n            )],\n            from: vec![TableWithJoins {\n                relation: TableFactor::Derived {\n                    lateral: false,\n                    subquery: Box::new(query),\n                    alias: Some(simple_table_alias(sql_ast::Ident::new(\n                        context.anchor.table_name.gen(),\n                    ))),\n                },\n                joins: vec![],\n            }],\n            ..default_select()\n        }))\n    };\n\n    Box::new(set_expr)\n}\n\nfn count_tables(transforms: &[Transform]) -> usize {\n    let mut count = 0;\n    for transform in transforms {\n        if let Transform::Join { .. } | Transform::From(_) = transform {\n            count += 1;\n        }\n    }\n\n    count\n}\n\n/// Extract the first expression from a projection for use in ORDER BY.\n/// Returns None if the projection is empty or only contains wildcards.\nfn first_expr_from_projection(projection: &[SelectItem]) -> Option<sql_ast::Expr> {\n    for item in projection {\n        match item {\n            SelectItem::UnnamedExpr(expr) => return Some(expr.clone()),\n            SelectItem::ExprWithAlias { alias, .. } => {\n                return Some(sql_ast::Expr::Identifier(alias.clone()));\n            }\n            SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _) => continue,\n        }\n    }\n    None\n}\n\n#[cfg(test)]\nmod test {\n    use insta::assert_snapshot;\n\n    #[test]\n    fn test_variable_after_aggregate() {\n        let query = &r#\"\n        from employees\n        group {title, emp_no} (\n            aggregate {emp_salary = average salary}\n        )\n        group {title} (\n            aggregate {avg_salary = average emp_salary}\n        )\n        \"#;\n\n        let sql_ast = crate::tests::compile(query).unwrap();\n\n        assert_snapshot!(sql_ast, @r\"\n        WITH table_0 AS (\n          SELECT\n            title,\n            AVG(salary) AS _expr_0\n          FROM\n            employees\n          GROUP BY\n            title,\n            emp_no\n        )\n        SELECT\n          title,\n          AVG(_expr_0) AS avg_salary\n        FROM\n          table_0\n        GROUP BY\n          title\n        \");\n    }\n\n    #[test]\n    fn test_derive_filter() {\n        // I suspect that the anchoring algorithm has a architectural flaw:\n        // it assumes that it can materialize all columns, even if their\n        // Compute is in a prior CTE. The problem is that because anchoring is\n        // computed back-to-front, we don't know where Compute will end up when\n        // materializing following transforms.\n        //\n        // If algorithm is changed to be front-to-back, preprocess_reorder can\n        // be (must be) removed.\n\n        let query = &r#\"\n        from employees\n        derive {global_rank = rank country}\n        filter country == \"USA\"\n        derive {rank = rank country}\n        \"#;\n\n        let sql_ast = crate::tests::compile(query).unwrap();\n\n        assert_snapshot!(sql_ast, @r\"\n        WITH table_0 AS (\n          SELECT\n            *,\n            RANK() OVER () AS global_rank\n          FROM\n            employees\n        )\n        SELECT\n          *,\n          RANK() OVER () AS rank\n        FROM\n          table_0\n        WHERE\n          country = 'USA'\n        \");\n    }\n\n    #[test]\n    fn test_filter_windowed() {\n        // #806\n        let query = &r#\"\n        from tbl1\n        filter (average bar) > 3\n        \"#;\n\n        assert_snapshot!(crate::tests::compile(query).unwrap(), @r\"\n        WITH table_0 AS (\n          SELECT\n            *,\n            AVG(bar) OVER () AS _expr_0\n          FROM\n            tbl1\n        )\n        SELECT\n          *\n        FROM\n          table_0\n        WHERE\n          _expr_0 > 3\n        \");\n    }\n\n    #[test]\n    fn test_distinct_on_with_aggregate() {\n        // #5556: DISTINCT ON with aggregate should include wildcard\n        let query = &r#\"\n        prql target:sql.postgres\n\n        from t1\n        group {id, name} (take 1)\n        aggregate {c=count this}\n        \"#;\n\n        assert_snapshot!(crate::tests::compile(query).unwrap(), @r\"\n        WITH table_0 AS (\n          SELECT\n            DISTINCT ON (id, name) *\n          FROM\n            t1\n        )\n        SELECT\n          COUNT(*) AS c\n        FROM\n          table_0\n        \");\n    }\n\n    #[test]\n    fn test_join_with_inaccessible_table() {\n        // issue #5280: join referencing table not accessible in current scope\n        let query = r#\"\n        from c = companies\n        join ca = companies_addresses (c.tax_code == ca.company)\n        group c.tax_code (\n          join a = addresses (a.id == ca.address)\n          sort {-ca.created_at}\n          take 2..\n        )\n        sort tax_code\n        \"#;\n\n        let err = crate::tests::compile(query).unwrap_err();\n        assert!(err.to_string().contains(\"not accessible in this context\"));\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/keywords.rs",
    "content": "use std::collections::{HashMap, HashSet};\nuse std::sync::OnceLock;\n\nuse sqlparser::keywords::{\n    Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX, RESERVED_FOR_COLUMN_ALIAS, RESERVED_FOR_TABLE_ALIAS,\n};\n\nuse crate::sql::dialect::Dialect;\n\n/// True for keywords which we want to quote when translating to SQL.\n///\n/// Currently we're being fairly permissive (over-quoting is not a big concern).\n// We're not including the full list from `SQL_KEYWORDS`, as that has terms such\n// as `ID`, instead we bring a few dialects' keywords in.\npub(super) fn is_keyword(ident: &str, dialect: &Dialect) -> bool {\n    let ident = ident.to_ascii_uppercase();\n\n    sql_keywords().contains(ident.as_str()) || dialect_keywords(dialect).contains(ident.as_str())\n}\n\nfn dialect_keywords(dialect: &Dialect) -> &'static HashSet<&'static str> {\n    match dialect {\n        Dialect::Redshift => redshift_keywords(),\n        _ => empty_keywords(),\n    }\n}\n\nfn empty_keywords() -> &'static HashSet<&'static str> {\n    static EMPTY: OnceLock<HashSet<&str>> = OnceLock::new();\n    EMPTY.get_or_init(HashSet::new)\n}\n\nfn redshift_keywords() -> &'static HashSet<&'static str> {\n    static REDSHIFT: OnceLock<HashSet<&str>> = OnceLock::new();\n    REDSHIFT.get_or_init(|| {\n        let mut m = HashSet::new();\n        m.extend(REDSHIFT_KEYWORDS);\n        m\n    })\n}\n\nfn sql_keywords() -> &'static HashSet<&'static str> {\n    static SQL_KEYWORDS: OnceLock<HashSet<&str>> = OnceLock::new();\n    SQL_KEYWORDS.get_or_init(|| {\n        let mut m = HashSet::new();\n        m.extend(SQLITE_KEYWORDS);\n        m.extend(POSTGRES_KEYWORDS);\n        m.extend(DUCKDB_KEYWORDS);\n        m.extend(BIGQUERY_KEYWORDS);\n\n        let reverse_index: HashMap<&Keyword, usize> = ALL_KEYWORDS_INDEX\n            .iter()\n            .enumerate()\n            .map(|(idx, kw)| (kw, idx))\n            .collect();\n\n        m.extend(\n            RESERVED_FOR_COLUMN_ALIAS\n                .iter()\n                .map(|x| ALL_KEYWORDS[reverse_index[x]]),\n        );\n\n        m.extend(\n            RESERVED_FOR_TABLE_ALIAS\n                .iter()\n                .map(|x| ALL_KEYWORDS[reverse_index[x]]),\n        );\n        m\n    })\n}\n\nconst SQLITE_KEYWORDS: &[&str] = &[\n    \"ABORT\",\n    \"ACTION\",\n    \"ADD\",\n    \"AFTER\",\n    \"ALL\",\n    \"ALTER\",\n    \"ALWAYS\",\n    \"ANALYZE\",\n    \"AND\",\n    \"AS\",\n    \"ASC\",\n    \"ATTACH\",\n    \"AUTOINCREMENT\",\n    \"BEFORE\",\n    \"BEGIN\",\n    \"BETWEEN\",\n    \"BY\",\n    \"CASCADE\",\n    \"CASE\",\n    \"CAST\",\n    \"CHECK\",\n    \"COLLATE\",\n    \"COLUMN\",\n    \"COMMIT\",\n    \"CONFLICT\",\n    \"CONSTRAINT\",\n    \"CREATE\",\n    \"CROSS\",\n    \"CURRENT\",\n    \"CURRENT_DATE\",\n    \"CURRENT_TIME\",\n    \"CURRENT_TIMESTAMP\",\n    \"DATABASE\",\n    \"DEFAULT\",\n    \"DEFERRABLE\",\n    \"DEFERRED\",\n    \"DELETE\",\n    \"DESC\",\n    \"DETACH\",\n    \"DISTINCT\",\n    \"DO\",\n    \"DROP\",\n    \"EACH\",\n    \"ELSE\",\n    \"END\",\n    \"ESCAPE\",\n    \"EXCEPT\",\n    \"EXCLUDE\",\n    \"EXCLUSIVE\",\n    \"EXISTS\",\n    \"EXPLAIN\",\n    \"FAIL\",\n    \"FILTER\",\n    \"FIRST\",\n    \"FOLLOWING\",\n    \"FOR\",\n    \"FOREIGN\",\n    \"FROM\",\n    \"FULL\",\n    \"GENERATED\",\n    \"GLOB\",\n    \"GROUP\",\n    \"GROUPS\",\n    \"HAVING\",\n    \"IF\",\n    \"IGNORE\",\n    \"IMMEDIATE\",\n    \"IN\",\n    \"INDEX\",\n    \"INDEXED\",\n    \"INITIALLY\",\n    \"INNER\",\n    \"INSERT\",\n    \"INSTEAD\",\n    \"INTERSECT\",\n    \"INTO\",\n    \"IS\",\n    \"ISNULL\",\n    \"JOIN\",\n    \"KEY\",\n    \"LAST\",\n    \"LEFT\",\n    \"LIKE\",\n    \"LIMIT\",\n    \"MATCH\",\n    \"MATERIALIZED\",\n    \"NATURAL\",\n    \"NO\",\n    \"NOT\",\n    \"NOTHING\",\n    \"NOTNULL\",\n    \"NULL\",\n    \"NULLS\",\n    \"OF\",\n    \"OFFSET\",\n    \"ON\",\n    \"OR\",\n    \"ORDER\",\n    \"OTHERS\",\n    \"OUTER\",\n    \"OVER\",\n    \"PARTITION\",\n    \"PLAN\",\n    \"PRAGMA\",\n    \"PRECEDING\",\n    \"PRIMARY\",\n    \"QUERY\",\n    \"RAISE\",\n    \"RANGE\",\n    \"RECURSIVE\",\n    \"REFERENCES\",\n    \"REGEXP\",\n    \"REINDEX\",\n    \"RELEASE\",\n    \"RENAME\",\n    \"REPLACE\",\n    \"RESTRICT\",\n    \"RETURNING\",\n    \"RIGHT\",\n    \"ROLLBACK\",\n    \"ROW\",\n    \"ROWS\",\n    \"SAVEPOINT\",\n    \"SELECT\",\n    \"SET\",\n    \"TABLE\",\n    \"TEMP\",\n    \"TEMPORARY\",\n    \"THEN\",\n    \"TIES\",\n    \"TO\",\n    \"TRANSACTION\",\n    \"TRIGGER\",\n    \"UNBOUNDED\",\n    \"UNION\",\n    \"UNIQUE\",\n    \"UPDATE\",\n    \"USING\",\n    \"VACUUM\",\n    \"VALUES\",\n    \"VIEW\",\n    \"VIRTUAL\",\n    \"WHEN\",\n    \"WHERE\",\n    \"WINDOW\",\n    \"WITH\",\n    \"WITHOUT\",\n];\n\n// Copy table from\n// <https://www.postgresql.org/docs/current/sql-keywords-appendix.html>, then run:\n//\n//    pbpaste | rg '^\\w+ *\\treserved' | choose 0 | rg '(.*)' -r '\"$1\",' | pbcopy\nconst POSTGRES_KEYWORDS: &[&str] = &[\n    \"ALL\",\n    \"ANALYSE\",\n    \"ANALYZE\",\n    \"AND\",\n    \"ANY\",\n    \"ARRAY\",\n    \"AS\",\n    \"ASC\",\n    \"ASYMMETRIC\",\n    \"AUTHORIZATION\",\n    \"BINARY\",\n    \"BOTH\",\n    \"CASE\",\n    \"CAST\",\n    \"CHECK\",\n    \"COLLATE\",\n    \"COLLATION\",\n    \"COLUMN\",\n    \"CONCURRENTLY\",\n    \"CONSTRAINT\",\n    \"CREATE\",\n    \"CROSS\",\n    \"CURRENT_CATALOG\",\n    \"CURRENT_DATE\",\n    \"CURRENT_ROLE\",\n    \"CURRENT_SCHEMA\",\n    \"CURRENT_TIME\",\n    \"CURRENT_TIMESTAMP\",\n    \"CURRENT_USER\",\n    \"DEFAULT\",\n    \"DEFERRABLE\",\n    \"DESC\",\n    \"DISTINCT\",\n    \"DO\",\n    \"ELSE\",\n    \"END\",\n    \"EXCEPT\",\n    \"FALSE\",\n    \"FETCH\",\n    \"FOR\",\n    \"FOREIGN\",\n    \"FREEZE\",\n    \"FROM\",\n    \"FULL\",\n    \"GRANT\",\n    \"GROUP\",\n    \"HAVING\",\n    \"ILIKE\",\n    \"IN\",\n    \"INITIALLY\",\n    \"INNER\",\n    \"INTERSECT\",\n    \"INTO\",\n    \"IS\",\n    \"ISNULL\",\n    \"JOIN\",\n    \"LATERAL\",\n    \"LEADING\",\n    \"LEFT\",\n    \"LIKE\",\n    \"LIMIT\",\n    \"LOCALTIME\",\n    \"LOCALTIMESTAMP\",\n    \"NATURAL\",\n    \"NOT\",\n    \"NOTNULL\",\n    \"NULL\",\n    \"OFFSET\",\n    \"ON\",\n    \"ONLY\",\n    \"OR\",\n    \"ORDER\",\n    \"OUTER\",\n    \"OVERLAPS\",\n    \"PLACING\",\n    \"PRIMARY\",\n    \"REFERENCES\",\n    \"RETURNING\",\n    \"RIGHT\",\n    \"SELECT\",\n    \"SESSION_USER\",\n    \"SIMILAR\",\n    \"SOME\",\n    \"SYMMETRIC\",\n    \"SYSTEM_USER\",\n    \"TABLE\",\n    \"TABLESAMPLE\",\n    \"THEN\",\n    \"TO\",\n    \"TRAILING\",\n    \"TRUE\",\n    \"UNION\",\n    \"UNIQUE\",\n    \"USER\",\n    \"USING\",\n    \"VARIADIC\",\n    \"VERBOSE\",\n    \"WHEN\",\n    \"WHERE\",\n    \"WINDOW\",\n    \"WITH\",\n];\n\n// In duckdb:\n//\n//   .output\n//   .mode list\n//   .headers off\n//   .output keywords.txt\n//   SELECT '\"'||UPPER(keyword_name)||'\",' FROM duckdb_keywords() WHERE keyword_category='reserved';\n//   .output\n//\nconst DUCKDB_KEYWORDS: &[&str] = &[\n    \"ALL\",\n    \"ANALYSE\",\n    \"ANALYZE\",\n    \"AND\",\n    \"ANY\",\n    \"ARRAY\",\n    \"AS\",\n    \"ASC\",\n    \"ASYMMETRIC\",\n    \"BOTH\",\n    \"CASE\",\n    \"CAST\",\n    \"CHECK\",\n    \"COLLATE\",\n    \"COLUMN\",\n    \"CONSTRAINT\",\n    \"CREATE\",\n    \"DEFAULT\",\n    \"DEFERRABLE\",\n    \"DESC\",\n    \"DESCRIBE\",\n    \"DISTINCT\",\n    \"DO\",\n    \"ELSE\",\n    \"END\",\n    \"EXCEPT\",\n    \"FALSE\",\n    \"FETCH\",\n    \"FOR\",\n    \"FOREIGN\",\n    \"FROM\",\n    \"GRANT\",\n    \"GROUP\",\n    \"HAVING\",\n    \"IN\",\n    \"INITIALLY\",\n    \"INTERSECT\",\n    \"INTO\",\n    \"LATERAL\",\n    \"LEADING\",\n    \"LIMIT\",\n    \"NOT\",\n    \"NULL\",\n    \"OFFSET\",\n    \"ON\",\n    \"ONLY\",\n    \"OR\",\n    \"ORDER\",\n    \"PIVOT\",\n    \"PIVOT_LONGER\",\n    \"PIVOT_WIDER\",\n    \"PLACING\",\n    \"PRIMARY\",\n    \"QUALIFY\",\n    \"REFERENCES\",\n    \"RETURNING\",\n    \"SELECT\",\n    \"SHOW\",\n    \"SOME\",\n    \"SUMMARIZE\",\n    \"SYMMETRIC\",\n    \"TABLE\",\n    \"THEN\",\n    \"TO\",\n    \"TRAILING\",\n    \"TRUE\",\n    \"UNION\",\n    \"UNIQUE\",\n    \"UNPIVOT\",\n    \"USING\",\n    \"VARIADIC\",\n    \"WHEN\",\n    \"WHERE\",\n    \"WINDOW\",\n    \"WITH\",\n];\n\n// source reserved keywords GoogleSQL\n// <https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords>\nconst BIGQUERY_KEYWORDS: &[&str] = &[\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// Source reserved keywords from Amazon Redshift\n// https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html\n// Only including keywords that are NOT in the common SQL keywords above\nconst REDSHIFT_KEYWORDS: &[&str] = &[\n    \"AES128\",\n    \"AES256\",\n    \"ALLOWOVERWRITE\",\n    \"BACKUP\",\n    \"BLANKSASNULL\",\n    \"BYTEDICT\",\n    \"BZIP2\",\n    \"CREDENTIALS\",\n    \"DEFRAG\",\n    \"DEFLATE\",\n    \"DELTA\",\n    \"DELTA32K\",\n    \"EMPTYASNULL\",\n    \"ENCODE\",\n    \"ENCRYPT\",\n    \"ENCRYPTION\",\n    \"EXPLICIT\",\n    \"GLOBALDICT256\",\n    \"GLOBALDICT64K\",\n    \"GZIP\",\n    \"IDENTITY\",\n    \"LUN\",\n    \"LUNS\",\n    \"LZO\",\n    \"LZOP\",\n    \"MINUS\",\n    \"MOSTLY13\",\n    \"MOSTLY32\",\n    \"MOSTLY8\",\n    \"OFFLINE\",\n    \"OID\",\n    \"PARALLEL\",\n    \"PERCENT\",\n    \"PERMISSIONS\",\n    \"RAW\",\n    \"READRATIO\",\n    \"RECOVER\",\n    \"REJECTLOG\",\n    \"RESORT\",\n    \"RESTORE\",\n    \"SNAPSHOT\",\n    \"SYSDATE\",\n    \"SYSTEM\",\n    \"TAG\",\n    \"TDES\",\n    \"TEXT255\",\n    \"TEXT32K\",\n    \"TIME\",\n    \"TIMESTAMP\",\n    \"TOP\",\n    \"TRUNCATECOLUMNS\",\n    \"WALLET\",\n];\n\n#[test]\nfn test_sql_keywords() {\n    assert!(is_keyword(\"from\", &Dialect::Generic));\n    assert!(is_keyword(\"user\", &Dialect::Generic));\n    // Redshift-specific keywords should only be keywords for Redshift\n    assert!(is_keyword(\"time\", &Dialect::Redshift));\n    assert!(!is_keyword(\"time\", &Dialect::Postgres));\n    assert!(!is_keyword(\"time\", &Dialect::Generic));\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/mod.rs",
    "content": "//! Backend for translating RQ into SQL\n\nmod dialect;\nmod gen_expr;\nmod gen_projection;\nmod gen_query;\nmod keywords;\nmod operators;\nmod pq;\n\npub use dialect::{Dialect, SupportLevel};\npub use pq::ast as pq_ast;\n\nuse self::dialect::DialectHandler;\nuse self::pq::ast::Cte;\nuse self::pq::context::AnchorContext;\nuse crate::debug;\nuse crate::ir::rq;\nuse crate::Result;\nuse crate::{compiler_version, Options};\n\n/// Translate a PRQL AST into a SQL string.\npub fn compile(query: rq::RelationalQuery, options: &Options) -> Result<String> {\n    let crate::Target::Sql(dialect) = options.target;\n    let sql_ast = gen_query::translate_query(query, dialect)?;\n\n    let sql = sql_ast.to_string();\n\n    // formatting\n    let sql = if options.format {\n        let formatted = sqlformat::format(\n            &sql,\n            &sqlformat::QueryParams::default(),\n            &sqlformat::FormatOptions::default(),\n        );\n\n        formatted + \"\\n\"\n    } else {\n        sql\n    };\n\n    debug::log_entry(|| debug::DebugEntryKind::ReprSql(sql.clone()));\n\n    // signature\n    let sql = if options.signature_comment {\n        let pre = if options.format { \"\\n\" } else { \" \" };\n        let post = if options.format { \"\\n\" } else { \"\" };\n        let target = dialect\n            .map(|d| format!(\"target:sql.{d} \"))\n            .unwrap_or_default();\n        let signature = format!(\n            \"{pre}-- Generated by PRQL compiler version:{} {}(https://prql-lang.org){post}\",\n            compiler_version(),\n            target,\n        );\n        sql + &signature\n    } else {\n        sql\n    };\n\n    Ok(sql)\n}\n\n#[derive(Debug)]\nstruct Context {\n    pub dialect: Box<dyn DialectHandler>,\n    pub dialect_enum: Dialect,\n\n    pub anchor: AnchorContext,\n\n    // stuff regarding current query\n    query: QueryOpts,\n\n    // stuff regarding parent queries\n    query_stack: Vec<QueryOpts>,\n\n    pub ctes: Vec<Cte>,\n}\n\n#[derive(Clone, Debug)]\nstruct QueryOpts {\n    /// When true, column references will not include table names prefixes.\n    pub omit_ident_prefix: bool,\n\n    /// True iff codegen should generate expressions before SELECT's projection is applied.\n    /// For example:\n    /// - WHERE needs `pre_projection=true`, but\n    /// - ORDER BY needs `pre_projection=false`.\n    pub pre_projection: bool,\n\n    /// When false, queries will contain nested sub-queries instead of WITH CTEs.\n    pub allow_ctes: bool,\n\n    /// When false, * are not allowed.\n    pub allow_stars: bool,\n\n    /// True when translating function that will have an OVER clause.\n    pub window_function: bool,\n}\n\nimpl Default for QueryOpts {\n    fn default() -> Self {\n        QueryOpts {\n            omit_ident_prefix: false,\n            pre_projection: false,\n            allow_ctes: true,\n            allow_stars: true,\n            window_function: false,\n        }\n    }\n}\n\nimpl Context {\n    fn new(dialect: Dialect, anchor: AnchorContext) -> Self {\n        Context {\n            dialect: dialect.handler(),\n            dialect_enum: dialect,\n            anchor,\n            query: QueryOpts::default(),\n            query_stack: Vec::new(),\n            ctes: Vec::new(),\n        }\n    }\n\n    fn push_query(&mut self) {\n        self.query_stack.push(self.query.clone());\n    }\n\n    fn pop_query(&mut self) {\n        self.query = self.query_stack.pop().unwrap();\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use crate::compile;\n    use crate::Options;\n\n    #[test]\n    fn test_end_with_new_line() {\n        let sql = compile(\"from a\", &Options::default().no_signature()).unwrap();\n        assert_eq!(sql, \"SELECT\\n  *\\nFROM\\n  a\\n\")\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/operators.rs",
    "content": "use std::collections::HashMap;\nuse std::iter::zip;\nuse std::path::PathBuf;\nuse std::sync::OnceLock;\n\nuse itertools::Itertools;\n\nuse super::gen_expr::{translate_operand, ExprOrSource, SourceExpr};\nuse super::{Context, Dialect};\nuse crate::ir::{decl, pl, rq};\nuse crate::utils::Pluck;\nuse crate::Result;\nuse crate::{debug, semantic};\nuse crate::{Error, WithErrorInfo};\n\nfn std() -> &'static decl::Module {\n    static STD: OnceLock<decl::Module> = OnceLock::new();\n    STD.get_or_init(|| {\n        let _suppressed = debug::log_suppress();\n\n        let std_lib = crate::SourceTree::new(\n            [(\n                PathBuf::from(\"std.prql\"),\n                include_str!(\"./std.sql.prql\").to_string(),\n            )],\n            None,\n        );\n        let ast = crate::parser::parse(&std_lib).unwrap();\n        let context = semantic::resolve(ast).unwrap();\n\n        context.module\n    })\n}\n\npub(super) fn translate_operator_expr(expr: rq::Expr, ctx: &mut Context) -> Result<ExprOrSource> {\n    let (name, args) = expr.kind.into_operator().unwrap();\n\n    let source = translate_operator(name, args, ctx).with_span(expr.span)?;\n\n    Ok(ExprOrSource::Source(source))\n}\n\npub(super) fn translate_operator(\n    name: String,\n    args: Vec<rq::Expr>,\n    ctx: &mut Context,\n) -> Result<SourceExpr> {\n    let (func_def, binding_strength, window_frame, coalesce) =\n        find_operator_impl(&name, ctx.dialect_enum).unwrap();\n    let parent_binding_strength = binding_strength.unwrap_or(100);\n\n    let params = func_def\n        .named_params\n        .iter()\n        .chain(func_def.params.iter())\n        .map(|x| x.name.split('.').next_back().unwrap_or(x.name.as_str()));\n\n    let args: HashMap<&str, _> = zip(params, args).collect();\n\n    // body can only be an s-string\n    let body = match &func_def.body.kind {\n        pl::ExprKind::Literal(pl::Literal::Null) => {\n            return Err(Error::new_simple(format!(\n                \"operator {} is not supported for dialect {}\",\n                name, ctx.dialect_enum\n            )))\n        }\n        pl::ExprKind::SString(items) => items,\n        _ => panic!(\"Bad RQ operator implementation. Expected s-string or null\"),\n    };\n\n    let mut text = String::new();\n\n    for item in body {\n        match item {\n            pl::InterpolateItem::Expr { expr, format } => {\n                // s-string exprs can only contain idents\n                let ident = expr.kind.as_ident();\n                let ident = ident.as_ref().unwrap();\n\n                // lookup args\n                let arg = args.get(ident.name.as_str()).unwrap().clone();\n\n                // binding strength\n                let required_strength = format\n                    .as_ref()\n                    .and_then(|f| f.parse::<i32>().ok())\n                    .unwrap_or(parent_binding_strength);\n\n                // translate args\n                let arg = translate_operand(\n                    arg,\n                    false,\n                    required_strength,\n                    super::gen_expr::Associativity::Both,\n                    ctx,\n                )?;\n\n                text += &arg.into_source();\n            }\n            pl::InterpolateItem::String(s) => {\n                text += s;\n            }\n        }\n    }\n\n    let mut binding_strength = parent_binding_strength;\n\n    if !ctx.query.window_function {\n        if let Some(default) = coalesce {\n            text = format!(\"COALESCE({text}, {default})\");\n            binding_strength = 100;\n        }\n    }\n\n    Ok(SourceExpr {\n        text,\n        binding_strength,\n        window_frame,\n    })\n}\n\nfn find_operator_impl(\n    operator_name: &str,\n    dialect: Dialect,\n) -> Option<(&pl::Func, Option<i32>, bool, Option<String>)> {\n    let operator_name = operator_name.strip_prefix(\"std.\").unwrap();\n    let operator_ident = pl::Ident::from_path(\n        operator_name\n            .split('.')\n            .map(String::from)\n            .collect::<Vec<_>>(),\n    );\n\n    let dialect_module = std().get(&pl::Ident::from_name(dialect.to_string()));\n\n    let mut func_def = None;\n\n    if let Some(dialect_module) = dialect_module {\n        let module = dialect_module.kind.as_module().unwrap();\n        func_def = module.get(&operator_ident);\n    }\n\n    if func_def.is_none() {\n        func_def = std().get(&operator_ident);\n    }\n\n    let decl = func_def?;\n\n    let func_def = decl.kind.as_expr().unwrap();\n    let func_def = func_def.kind.as_func().unwrap();\n\n    let annotation = decl.annotations.iter().exactly_one().ok();\n    let mut annotation = annotation\n        .and_then(|x| into_tuple_items(*x.expr.clone()).ok())\n        .unwrap_or_default();\n\n    let binding_strength = pluck_annotation(&mut annotation, \"binding_strength\")\n        .and_then(|literal| literal.into_integer().ok())\n        .map(|int| int as i32);\n\n    let window_frame = pluck_annotation(&mut annotation, \"window_frame\")\n        .and_then(|literal| literal.into_boolean().ok())\n        .unwrap_or_default();\n\n    let coalesce =\n        pluck_annotation(&mut annotation, \"coalesce\").and_then(|val| val.into_string().ok());\n\n    Some((func_def.as_ref(), binding_strength, window_frame, coalesce))\n}\n\nfn pluck_annotation(\n    annotation: &mut Vec<(String, pl::ExprKind)>,\n    name: &str,\n) -> Option<pl::Literal> {\n    annotation\n        .pluck(|(n, val)| if n == name { Ok(val) } else { Err((n, val)) })\n        .into_iter()\n        .next()\n        .and_then(|val| val.into_literal().ok())\n}\n\n/// Find the items in a `@{a=b}`. We're only using annotations with tuples;\n/// we can consider formalizing this constraint.\nfn into_tuple_items(expr: pl::Expr) -> Result<Vec<(String, pl::ExprKind)>, pl::Expr> {\n    match expr.kind {\n        pl::ExprKind::Tuple(items) => items\n            .into_iter()\n            .map(|item| Ok((item.alias.clone().unwrap(), item.kind)))\n            .collect(),\n        _ => Err(expr),\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/pq/anchor.rs",
    "content": "use std::collections::{HashMap, HashSet};\nuse std::fmt;\nuse std::ops::Deref;\n\nuse itertools::Itertools;\n\nuse super::ast::{PqMapper, SqlTransform};\nuse super::context::{AnchorContext, ColumnDecl, RIId, RelationStatus, SqlTableDecl};\nuse crate::ir::generic::ColumnSort;\nuse crate::ir::rq::{\n    self, fold_column_sorts, fold_transform, CId, Compute, Expr, RelationColumn, RqFold, TableRef,\n    Transform,\n};\nuse crate::sql::pq::context::RelationAdapter;\nuse crate::sql::pq::positional_mapping::compute_positional_mappings;\nuse crate::Result;\n\n/// Extract last part of pipeline that is able to \"fit\" into a single SELECT statement.\n/// Remaining proceeding pipeline is declared as a table and stored in AnchorContext.\npub(super) fn extract_atomic(\n    pipeline: Vec<SqlTransform>,\n    ctx: &mut AnchorContext,\n) -> Vec<SqlTransform> {\n    let output = ctx.determine_select_columns(&pipeline);\n    let output = ctx.positional_mapping.apply_active_mapping(output);\n\n    let (preceding, atomic) = split_off_back(pipeline, output.clone(), ctx);\n\n    let atomic = if let Some(preceding) = preceding {\n        log::debug!(\n            \"pipeline split after {}\",\n            preceding.last().unwrap().as_str()\n        );\n        anchor_split(ctx, preceding, atomic)\n    } else {\n        atomic\n    };\n\n    // sometimes, additional columns will be added into select, because they are needed for\n    // other clauses. To filter them out, we use an additional limiting SELECT.\n    let output: Vec<_> = CidRedirector::redirect_cids(output, &atomic, ctx);\n    let select_cols = atomic\n        .iter()\n        .find_map(|x| x.as_super().and_then(|y| y.as_select()))\n        .unwrap();\n    if select_cols.iter().any(|c| !output.contains(c)) {\n        log::debug!(\n            \"appending a projection SELECT, because previous one contained un-selected columns\"\n        );\n\n        // duplicate Select for purposes of anchor_split\n        let duplicated_select = SqlTransform::Super(Transform::Select(select_cols.clone()));\n        let mut atomic = atomic;\n        atomic.push(duplicated_select);\n\n        // construct the new SELECT\n        let limited_view = vec![SqlTransform::Super(Transform::Select(output))];\n\n        return anchor_split(ctx, atomic, limited_view);\n    }\n\n    atomic\n}\n\n/// Splits pipeline into two parts, such that the second part contains\n/// maximum number of transforms while \"fitting\" into a SELECT query.\n///\n/// Returns optional remaining preceding pipeline and the atomic pipeline.\npub(super) fn split_off_back(\n    mut pipeline: Vec<SqlTransform>,\n    output: Vec<CId>,\n    ctx: &mut AnchorContext,\n) -> (Option<Vec<SqlTransform>>, Vec<SqlTransform>) {\n    if pipeline.is_empty() {\n        return (None, Vec::new());\n    }\n\n    let mapping_before = compute_positional_mappings(&pipeline, None);\n\n    log::debug!(\"traversing pipeline to obtain columns: {output:?}\");\n\n    let mut following_transforms: HashSet<String> = HashSet::new();\n\n    let mut inputs_required = Requirements::from_cids(output.iter())\n        .allow_up_to(Complexity::highest())\n        .should_select(true);\n\n    let mut inputs_avail = HashSet::new();\n\n    // iterate backwards\n    let mut curr_pipeline_rev = Vec::new();\n    'pipeline: while let Some(transform) = pipeline.pop() {\n        // stop if split is needed\n        let split = is_split_required(&transform, &mut following_transforms);\n        if split {\n            log::debug!(\"split required after {}\", transform.as_str());\n            log::debug!(\".. following={following_transforms:?}\");\n            pipeline.push(transform);\n            break;\n        }\n\n        // anchor and record all requirements\n        let required = get_requirements(&transform, &following_transforms, &inputs_required);\n        log::debug!(\".. transform {} requires {required:?}\", transform.as_str(),);\n        inputs_required = inputs_required.append(required.clone());\n\n        match &transform {\n            SqlTransform::Super(Transform::Compute(compute)) => {\n                let (can_mat, max_complexity) = can_materialize(compute, &inputs_required);\n                if can_mat {\n                    log::debug!(\"materializing {:?}\", compute.id);\n                    inputs_avail.insert(compute.id);\n\n                    // add transitive dependencies\n                    inputs_required = inputs_required\n                        .append(required.allow_up_to(max_complexity).should_select(false));\n                } else {\n                    pipeline.push(transform);\n                    break;\n                }\n            }\n            SqlTransform::Super(Transform::Aggregate { compute, .. }) => {\n                for cid in compute {\n                    let decl = &ctx.column_decls[cid];\n                    if let ColumnDecl::Compute(compute) = decl {\n                        if !can_materialize(compute, &inputs_required).0 {\n                            pipeline.push(transform);\n                            break 'pipeline;\n                        }\n                    }\n                }\n            }\n            SqlTransform::From(with) | SqlTransform::Join { with, .. } => {\n                let relation = ctx.relation_instances.get_mut(with).unwrap();\n                for (_, cid) in &relation.table_ref.columns {\n                    inputs_avail.insert(*cid);\n                }\n            }\n            _ => (),\n        }\n\n        // push into current pipeline\n        if !matches!(transform, SqlTransform::Super(Transform::Select(_))) {\n            curr_pipeline_rev.push(transform);\n        }\n    }\n\n    let selected = inputs_required\n        .iter()\n        .filter(|r| r.selected)\n        .map(|r| r.col)\n        .collect_vec();\n\n    log::debug!(\"finished table:\");\n    log::debug!(\".. avail={inputs_avail:?}\");\n\n    let required = inputs_required.iter().map(|r| r.col).unique().collect_vec();\n    log::debug!(\".. required={required:?}\");\n\n    let missing = required\n        .into_iter()\n        .filter(|i| !inputs_avail.contains(i))\n        .collect_vec();\n    log::debug!(\".. missing={missing:?}\");\n\n    // figure out SELECT columns\n    {\n        // output cols must preserve duplicates, but selected inputs has to be deduplicated\n        let mut output = output;\n        for c in selected {\n            if !output.contains(&c) {\n                output.push(c);\n            }\n        }\n\n        curr_pipeline_rev.push(SqlTransform::Super(Transform::Select(output)));\n    }\n\n    let remaining_pipeline = if pipeline.is_empty() {\n        None\n    } else {\n        // drop inputs that were satisfied in current pipeline\n        pipeline.push(SqlTransform::Super(Transform::Select(missing)));\n\n        Some(pipeline)\n    };\n\n    curr_pipeline_rev.reverse();\n\n    // This will compare columns for order sensitive transform and correct it in subsequent relation.\n    let mapping_after = compute_positional_mappings(&curr_pipeline_rev, Some(&inputs_required));\n    for (riid, after) in mapping_after {\n        if let Some((_, before)) = mapping_before.iter().find(|(r, _)| &riid == r) {\n            ctx.positional_mapping\n                .compute_and_store_mapping(before, &after, &riid);\n        }\n    }\n\n    (remaining_pipeline, curr_pipeline_rev)\n}\n\nfn can_materialize(compute: &Compute, inputs_required: &[Requirement]) -> (bool, Complexity) {\n    let complexity = infer_complexity(compute);\n\n    let required = inputs_required\n        .iter()\n        .filter(|r| r.col == compute.id)\n        .fold(Complexity::highest(), |c, r| {\n            Complexity::min(c, r.max_complexity)\n        });\n\n    let can_materialize = complexity <= required;\n    if !can_materialize {\n        // cannot materialize here, complexity is greater than what's required here\n        log::debug!(\n            \"{:?} has complexity {complexity:?}, but is required to have at most {required:?}\",\n            compute.id\n        );\n    }\n    (can_materialize, required)\n}\n\n/// Applies adjustments to second part of a pipeline when it's split:\n/// - append Select to proceeding pipeline\n/// - prepend From to atomic pipeline\n/// - redefine columns materialized in atomic pipeline\n/// - redirect all references to original columns to the new ones\npub(super) fn anchor_split(\n    ctx: &mut AnchorContext,\n    preceding: Vec<SqlTransform>,\n    atomic: Vec<SqlTransform>,\n) -> Vec<SqlTransform> {\n    let new_tid = ctx.tid.gen();\n\n    let preceding_select = &preceding.last().unwrap().as_super().unwrap();\n    let cols_at_split = preceding_select.as_select().unwrap();\n\n    log::debug!(\"split pipeline, first pipeline output: {cols_at_split:?}\");\n\n    // redefine columns of the atomic pipeline\n    let mut cid_redirects = HashMap::<CId, CId>::new();\n    let mut new_columns = Vec::new();\n    let mut used_new_names = HashSet::new();\n    for old_cid in cols_at_split {\n        let new_cid = ctx.cid.gen();\n\n        let old_name = ctx.ensure_column_name(*old_cid).cloned();\n\n        let mut new_name = old_name;\n        if let Some(new) = &mut new_name {\n            if used_new_names.contains(new) {\n                *new = ctx.col_name.gen();\n                ctx.column_names.insert(*old_cid, new.clone());\n            }\n\n            used_new_names.insert(new.clone());\n            ctx.column_names.insert(new_cid, new.clone());\n        }\n\n        let old_def = ctx.column_decls.get(old_cid).unwrap();\n\n        let col = match old_def {\n            ColumnDecl::RelationColumn(_, _, RelationColumn::Wildcard) => RelationColumn::Wildcard,\n            _ => RelationColumn::Single(new_name),\n        };\n\n        new_columns.push((col, new_cid));\n        cid_redirects.insert(*old_cid, new_cid);\n    }\n\n    // define a new table\n    let columns = cols_at_split\n        .iter()\n        .map(|_| RelationColumn::Single(None))\n        .collect_vec();\n    ctx.table_decls.insert(\n        new_tid,\n        SqlTableDecl {\n            id: new_tid,\n            name: None,\n            relation: RelationStatus::NotYetDefined(RelationAdapter::Preprocessed(\n                preceding, columns,\n            )),\n            redirect_to: None,\n        },\n    );\n\n    // define instance of that table\n    let riid = ctx.create_relation_instance(\n        TableRef {\n            source: new_tid,\n            name: None,\n            columns: new_columns,\n            prefer_cte: true,\n        },\n        cid_redirects,\n    );\n\n    // adjust second part: prepend from and rewrite expressions to use new columns\n    let mut second = atomic;\n    second.insert(0, SqlTransform::From(riid));\n\n    CidRedirector::redirect_pipeline(second, ctx)\n}\n\n/// Determines whether a pipeline must be split at a transform to\n/// fit into one SELECT statement.\n///\n/// `following` contain names of following transforms in the pipeline.\nfn is_split_required(transform: &SqlTransform, following: &mut HashSet<String>) -> bool {\n    // Pipeline must be split when there is a transform that is out of order:\n    // - from (max 1x),\n    // - join (no limit),\n    // - filters (for WHERE)\n    // - aggregate (max 1x)\n    // - filters (for HAVING)\n    // - compute (no limit)\n    // - sort (no limit)\n    // - take (no limit)\n    // - distinct\n    // - append/except/intersect (no limit)\n    // - loop (max 1x)\n    //\n    // Select is not affected by the order.\n    use SqlTransform::Super;\n    use Transform::*;\n\n    // Compute for aggregation does not count as a real compute,\n    // because it's done within the aggregation\n    if let Super(Compute(decl)) = transform {\n        if decl.is_aggregation {\n            return false;\n        }\n    }\n\n    fn contains_any<const C: usize>(set: &HashSet<String>, elements: [&'static str; C]) -> bool {\n        for t in elements {\n            if set.contains(t) {\n                return true;\n            }\n        }\n        false\n    }\n\n    let split = match transform {\n        SqlTransform::From(_) => contains_any(following, [\"From\"]),\n        SqlTransform::Join { .. } => contains_any(following, [\"From\"]),\n        Super(Aggregate { .. }) => {\n            contains_any(following, [\"From\", \"Join\", \"Aggregate\", \"Compute\"])\n        }\n        Super(Filter(_)) => contains_any(following, [\"From\", \"Join\"]),\n        Super(Compute(_)) => {\n            // Don't split between Compute and Filter when there's an Aggregate in following.\n            // Filter after Aggregate becomes HAVING in the same SELECT, so all preceding\n            // Computes (including those with aggregation functions like SUM) must stay\n            // with the Aggregate to get proper GROUP BY.\n            if following.contains(\"Aggregate\") {\n                contains_any(following, [\"From\", \"Join\"])\n            } else {\n                contains_any(following, [\"From\", \"Join\", /* \"Aggregate\" */ \"Filter\"])\n            }\n        }\n\n        // Sort will be pushed down the CTEs, so there is no point in splitting for it.\n        // Super(Sort(_)) => contains_any(following, [\"From\", \"Join\", \"Compute\", \"Aggregate\"]),\n        Super(Take(_)) => contains_any(\n            following,\n            [\"From\", \"Join\", \"Compute\", \"Filter\", \"Aggregate\", \"Sort\"],\n        ),\n        SqlTransform::DistinctOn(_) => contains_any(\n            following,\n            [\n                \"From\",\n                \"Join\",\n                \"Compute\",\n                \"Filter\",\n                \"Aggregate\",\n                \"Sort\",\n                \"Take\",\n                \"DistinctOn\",\n            ],\n        ),\n        SqlTransform::Distinct => contains_any(\n            following,\n            [\n                \"From\",\n                \"Join\",\n                \"Compute\",\n                \"Filter\",\n                \"Aggregate\",\n                \"Sort\",\n                \"Take\",\n            ],\n        ),\n        SqlTransform::Union { .. }\n        | SqlTransform::Except { .. }\n        | SqlTransform::Intersect { .. } => contains_any(\n            following,\n            [\n                \"From\",\n                \"Join\",\n                \"Compute\",\n                \"Filter\",\n                \"Aggregate\",\n                \"Sort\",\n                \"Take\",\n                \"Distinct\",\n            ],\n        ),\n        Super(Loop(_)) => !following.is_empty(),\n        _ => false,\n    };\n\n    if !split {\n        following.insert(transform.as_str().to_string());\n    }\n    split\n}\n\n/// An input requirement of a transform.\n#[derive(Clone)]\npub struct Requirement {\n    pub col: CId,\n\n    /// Maximum complexity with which this column can be expressed in this transform\n    pub max_complexity: Complexity,\n\n    /// True iff this column needs to be SELECTTed so it can be referenced in this transform\n    pub selected: bool,\n}\n\n#[derive(Clone, Default)]\npub struct Requirements(Vec<Requirement>);\n\n/// To iter on `Requirements` as if it's a simple `Vec`.\nimpl Deref for Requirements {\n    type Target = [Requirement];\n\n    fn deref(&self) -> &[Requirement] {\n        self.0.as_slice()\n    }\n}\n\n/// To debug `Requirements` as if it's a simple `Vec`.\nimpl fmt::Debug for Requirements {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{:?}\", self.0)\n    }\n}\n\nimpl Requirements {\n    /// Turns a list of `CId` into requirements with the least allowed complexity\n    /// and unselected by default.\n    pub fn from_cids<'a, I>(cids: I) -> Requirements\n    where\n        I: Iterator<Item = &'a CId>,\n    {\n        Requirements(\n            cids.cloned()\n                .map(|col| Requirement {\n                    col,\n                    max_complexity: Complexity::lowest(),\n                    selected: false,\n                })\n                .collect(),\n        )\n    }\n\n    /// Collect columns from the given `Expr` into requirements with\n    /// the least allowed complexity and unselected by default.\n    pub fn from_expr(expr: &Expr) -> Requirements {\n        let cids = CidCollector::collect(expr.clone());\n        Requirements::from_cids(cids.iter())\n    }\n\n    /// Moves all the elements of `other` into `self`, leaving `other` empty,\n    /// then return `self` for chainability.\n    pub fn append(mut self, mut other: Requirements) -> Requirements {\n        self.0.append(&mut other.0);\n        self\n    }\n\n    /// Set a maximum complexity to all stored requirements.\n    pub fn allow_up_to(mut self, max_complexity: Complexity) -> Self {\n        for r in &mut self.0 {\n            r.max_complexity = max_complexity;\n        }\n        self\n    }\n\n    /// Set a SELECT status to all stored requirements.\n    pub fn should_select(mut self, selected: bool) -> Self {\n        for r in &mut self.0 {\n            r.selected = selected;\n        }\n        self\n    }\n\n    pub fn is_selected(&self, id: &CId) -> bool {\n        self.0.iter().any(|r| r.selected && &r.col == id)\n    }\n\n    pub fn is_required(&self, id: &CId) -> bool {\n        self.0.iter().any(|r| &r.col == id)\n    }\n}\n\nimpl std::fmt::Debug for Requirement {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        std::fmt::Debug::fmt(&self.col, f)?;\n        f.write_str(\"-as-\")?;\n        std::fmt::Debug::fmt(&self.max_complexity, f)\n    }\n}\n\npub(super) fn get_requirements(\n    transform: &SqlTransform,\n    following: &HashSet<String>,\n    previous_requirements: &Requirements,\n) -> Requirements {\n    use SqlTransform::Super;\n\n    match transform {\n        Super(Transform::Aggregate { partition, .. }) => Requirements::from_cids(partition.iter()),\n\n        Super(Transform::Compute(compute)) if previous_requirements.is_required(&compute.id) => {\n            let requirements = Requirements::from_expr(&compute.expr).allow_up_to(\n                match infer_complexity(compute) {\n                    // plain expressions can be included in anything less complex than Aggregation\n                    Complexity::Plain => Complexity::Aggregation,\n\n                    // anything more complex can only use included in other plain expressions.\n                    // in other words: complex expressions (aggregation, window functions) cannot\n                    // be defined within other expressions.\n                    _ => Complexity::Plain,\n                },\n            );\n\n            if let Some(window) = &compute.window {\n                // TODO: what kind of exprs can be in window frame?\n                // window.frame\n\n                let window_cids = window\n                    .partition\n                    .iter()\n                    .chain(window.sort.iter().map(|s| &s.column));\n\n                requirements.append(Requirements::from_cids(window_cids))\n            } else {\n                requirements\n            }\n        }\n\n        Super(Transform::Filter(expr)) => {\n            Requirements::from_expr(expr).allow_up_to(if !following.contains(\"Aggregate\") {\n                Complexity::Aggregation\n            } else {\n                Complexity::Plain\n            })\n        }\n\n        // Aggregations require that all selected columns be wrapped in aggregate functions (e.g., SUM, COUNT).\n        Super(Transform::Sort(sorts)) if !following.contains(\"Aggregate\") => {\n            Requirements::from_cids(sorts.iter().map(|s| &s.column))\n                // we only use SELECTTed columns in ORDER BY, so the columns can have high complexity\n                .allow_up_to(Complexity::Aggregation)\n                .should_select(true)\n        }\n\n        SqlTransform::Sort(sorts) if !following.contains(\"Aggregate\") => {\n            Requirements::from_cids(sorts.iter().map(|s| &s.column))\n        }\n\n        SqlTransform::DistinctOn(partition) => Requirements::from_cids(partition.iter())\n            // Since there is aggregation anyway, columns can have any complexity\n            .allow_up_to(Complexity::highest()),\n\n        Super(Transform::Take(rq::Take { range, .. })) => [&range.start, &range.end]\n            .into_iter()\n            .flatten()\n            .map(Requirements::from_expr)\n            .fold(Requirements::default(), Requirements::append),\n\n        SqlTransform::Join { filter, .. } => Requirements::from_expr(filter),\n\n        _ => Requirements::default(),\n    }\n}\n\n/// Complexity of a column expressions.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\npub enum Complexity {\n    /// Simple non-aggregated and non-windowed expressions\n    Plain,\n    /// Expressions that cannot be used in GROUP BY (CASE)\n    NonGroup,\n    /// Non-aggregated expressions\n    Windowed,\n    /// Everything\n    Aggregation,\n}\n\nimpl Complexity {\n    const fn lowest() -> Self {\n        Self::Plain\n    }\n\n    const fn highest() -> Self {\n        Self::Aggregation\n    }\n}\n\npub fn infer_complexity(compute: &Compute) -> Complexity {\n    use Complexity::*;\n\n    if compute.window.is_some() {\n        Windowed\n    } else if compute.is_aggregation {\n        Aggregation\n    } else {\n        infer_complexity_expr(&compute.expr)\n    }\n}\n\npub fn infer_complexity_expr(expr: &Expr) -> Complexity {\n    match &expr.kind {\n        rq::ExprKind::Case(_) => Complexity::NonGroup,\n        rq::ExprKind::Operator { args, .. } => args\n            .iter()\n            .map(infer_complexity_expr)\n            .max()\n            .unwrap_or(Complexity::Plain),\n        rq::ExprKind::ColumnRef(_)\n        | rq::ExprKind::Literal(_)\n        | rq::ExprKind::SString(_)\n        | rq::ExprKind::Param(_) => Complexity::Plain,\n        rq::ExprKind::Array(elements) => elements\n            .iter()\n            .map(infer_complexity_expr)\n            .max()\n            .unwrap_or(Complexity::Plain),\n    }\n}\n\n#[derive(Default)]\npub struct CidCollector {\n    // we could use HashSet instead of Vec, but this caused nondeterministic\n    // results downstream\n    cids: Vec<CId>,\n}\n\nimpl CidCollector {\n    pub fn collect(expr: Expr) -> Vec<CId> {\n        let mut collector = CidCollector::default();\n        collector.fold_expr(expr).unwrap();\n        collector.cids\n    }\n\n    pub fn collect_t(t: Transform) -> (Transform, Vec<CId>) {\n        let mut collector = CidCollector::default();\n        let t = collector.fold_transform(t).unwrap();\n        (t, collector.cids)\n    }\n}\n\nimpl RqFold for CidCollector {\n    fn fold_cid(&mut self, cid: CId) -> Result<CId> {\n        self.cids.push(cid);\n        Ok(cid)\n    }\n}\n\npub(super) struct CidRedirector<'a> {\n    ctx: &'a mut AnchorContext,\n    cid_redirects: HashMap<CId, CId>,\n}\n\nimpl<'a> CidRedirector<'a> {\n    pub fn of_first_from(pipeline: &[SqlTransform], ctx: &'a mut AnchorContext) -> Option<Self> {\n        let from = pipeline.first()?.as_from()?;\n        let relation_instance = &ctx.relation_instances[from];\n        let cid_redirects = relation_instance.cid_redirects.clone();\n        Some(CidRedirector { ctx, cid_redirects })\n    }\n\n    pub fn redirect_pipeline(\n        pipeline: Vec<SqlTransform>,\n        ctx: &'a mut AnchorContext,\n    ) -> Vec<SqlTransform> {\n        let Some(mut redirector) = Self::of_first_from(&pipeline, ctx) else {\n            return pipeline;\n        };\n\n        redirector.fold_sql_transforms(pipeline).unwrap()\n    }\n\n    /// Redirects cids within a context of a pipeline.\n    /// This will find cid_redirects of the first From in the pipeline.\n    pub fn redirect_cids(\n        cids: Vec<CId>,\n        pipeline: &[SqlTransform],\n        ctx: &'a mut AnchorContext,\n    ) -> Vec<CId> {\n        // find cid_redirects\n        let Some(mut redirector) = Self::of_first_from(pipeline, ctx) else {\n            return cids;\n        };\n        redirector.fold_cids(cids).unwrap()\n    }\n\n    pub fn redirect_sorts(\n        sorts: Vec<ColumnSort<CId>>,\n        riid: &RIId,\n        ctx: &'a mut AnchorContext,\n    ) -> Vec<ColumnSort<CId>> {\n        let cid_redirects = ctx.relation_instances[riid].cid_redirects.clone();\n        log::debug!(\"redirect sorts {sorts:?} {riid:?} cid_redirects {cid_redirects:?}\");\n        let mut redirector = CidRedirector { ctx, cid_redirects };\n\n        fold_column_sorts(&mut redirector, sorts).unwrap()\n    }\n}\n\nimpl RqFold for CidRedirector<'_> {\n    fn fold_cid(&mut self, cid: CId) -> Result<CId> {\n        let v = self.cid_redirects.get(&cid).cloned().unwrap_or(cid);\n        log::debug!(\"mapping {cid:?} via {0:?} to {v:?}\", self.cid_redirects);\n        Ok(v)\n    }\n\n    fn fold_transform(&mut self, transform: Transform) -> Result<Transform> {\n        match transform {\n            Transform::Compute(compute) => {\n                let compute = self.fold_compute(compute)?;\n                self.ctx.register_compute(compute.clone());\n                Ok(Transform::Compute(compute))\n            }\n            _ => fold_transform(self, transform),\n        }\n    }\n}\n\nimpl PqMapper<RIId, RIId, Transform, Transform> for CidRedirector<'_> {\n    fn fold_rel(&mut self, rel: RIId) -> Result<RIId> {\n        Ok(rel)\n    }\n\n    fn fold_super(&mut self, sup: Transform) -> Result<Transform> {\n        self.fold_transform(sup)\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/pq/ast.rs",
    "content": "//! Sql Relational Query AST\n//!\n//! This IR dictates the structure of the resulting SQL query. This includes number of CTEs,\n//! position of sub-queries and set operations.\n\nuse enum_as_inner::EnumAsInner;\nuse itertools::Itertools;\nuse prqlc_parser::generic::InterpolateItem;\nuse serde::Serialize;\n\nuse super::context::RIId;\nuse crate::ir::generic::ColumnSort;\nuse crate::ir::pl::JoinSide;\nuse crate::ir::rq::{self, fold_column_sorts, RelationLiteral, RqFold};\nuse crate::Result;\n\n#[derive(Debug, Clone, Serialize)]\npub struct SqlQuery {\n    /// Common Table Expression (WITH clause)\n    pub ctes: Vec<Cte>,\n\n    /// The body of SELECT query.\n    pub main_relation: SqlRelation,\n}\n\n#[derive(Debug, Clone, EnumAsInner, Serialize)]\npub enum SqlRelation {\n    AtomicPipeline(Vec<SqlTransform<RelationExpr, ()>>),\n    Literal(RelationLiteral),\n    SString(Vec<InterpolateItem<rq::Expr>>),\n    Operator { name: String, args: Vec<rq::Expr> },\n}\n\n#[derive(Debug, Clone, Serialize)]\npub struct RelationExpr {\n    pub kind: RelationExprKind,\n\n    pub riid: RIId,\n}\n\n#[derive(Debug, Clone, Serialize)]\npub enum RelationExprKind {\n    Ref(rq::TId),\n    SubQuery(SqlRelation),\n}\n\n#[derive(Debug, Clone, Serialize)]\npub struct Cte {\n    pub tid: rq::TId,\n    pub kind: CteKind,\n}\n\n#[derive(Debug, Clone, Serialize)]\npub enum CteKind {\n    Normal(SqlRelation),\n    Loop {\n        initial: SqlRelation,\n        step: SqlRelation,\n    },\n}\n\n/// Similar to [rq::Transform], but closer to a SQL clause.\n///\n/// Uses two generic args that allows the compiler to work in multiple stages:\n/// - the first converts RQ to [SqlTransform<RIId, rq::Transform>],\n/// - the second compiles that to [SqlTransform<RelationExpr, ()>].\n#[derive(Debug, Clone, EnumAsInner, strum::AsRefStr, Serialize)]\npub enum SqlTransform<Rel = RIId, Super = rq::Transform> {\n    /// Contains [rq::Transform] during compilation. After finishing, this is emptied.\n    ///\n    /// For example, initial an RQ Append transform is wrapped as such:\n    ///\n    /// ```ignore\n    /// rq::Transform::Append(x) -> pq::SqlTransform::Super(rq::Transform::Append(x))\n    /// ```\n    ///\n    /// During preprocessing it is compiled to:\n    /// ```ignore\n    /// pq::SqlTransform::Super(rq::Transform::Append(_)) -> pq::SqlTransform::Union { .. }\n    /// ```\n    ///\n    /// At the end of PQ compilation, all `Super()` are either discarded or converted to their\n    /// PQ equivalents.\n    Super(Super),\n\n    From(Rel),\n    Select(Vec<rq::CId>),\n    Filter(rq::Expr),\n    Aggregate {\n        partition: Vec<rq::CId>,\n        compute: Vec<rq::CId>,\n    },\n    Sort(Vec<ColumnSort<rq::CId>>),\n    Take(rq::Take),\n    Join {\n        side: JoinSide,\n        with: Rel,\n        filter: rq::Expr,\n    },\n\n    Distinct,\n    DistinctOn(Vec<rq::CId>),\n    Except {\n        bottom: Rel,\n        distinct: bool,\n    },\n    Intersect {\n        bottom: Rel,\n        distinct: bool,\n    },\n    Union {\n        bottom: Rel,\n        distinct: bool,\n    },\n}\n\nimpl<Rel> SqlTransform<Rel> {\n    pub fn as_str(&self) -> &str {\n        match self {\n            SqlTransform::Super(t) => t.as_ref(),\n            _ => self.as_ref(),\n        }\n    }\n\n    pub fn into_super_and<T, F: FnOnce(rq::Transform) -> Result<T, rq::Transform>>(\n        self,\n        f: F,\n    ) -> Result<T, Self> {\n        self.into_super()\n            .and_then(|t| f(t).map_err(SqlTransform::Super))\n    }\n}\n\npub trait PqMapper<RelIn, RelOut, SuperIn, SuperOut>: RqFold {\n    fn fold_rel(&mut self, rel: RelIn) -> Result<RelOut>;\n\n    fn fold_super(&mut self, sup: SuperIn) -> Result<SuperOut>;\n\n    fn fold_sql_transforms(\n        &mut self,\n        transforms: Vec<SqlTransform<RelIn, SuperIn>>,\n    ) -> Result<Vec<SqlTransform<RelOut, SuperOut>>> {\n        transforms\n            .into_iter()\n            .map(|t| self.fold_sql_transform(t))\n            .try_collect()\n    }\n\n    fn fold_sql_transform(\n        &mut self,\n        transform: SqlTransform<RelIn, SuperIn>,\n    ) -> Result<SqlTransform<RelOut, SuperOut>> {\n        fold_sql_transform::<RelIn, RelOut, SuperIn, SuperOut, _>(self, transform)\n    }\n}\n\npub fn fold_sql_transform<\n    RelIn,\n    RelOut,\n    SuperIn,\n    SuperOut,\n    F: ?Sized + PqMapper<RelIn, RelOut, SuperIn, SuperOut>,\n>(\n    fold: &mut F,\n    transform: SqlTransform<RelIn, SuperIn>,\n) -> Result<SqlTransform<RelOut, SuperOut>> {\n    Ok(match transform {\n        SqlTransform::Super(t) => SqlTransform::Super(fold.fold_super(t)?),\n\n        SqlTransform::From(rel) => SqlTransform::From(fold.fold_rel(rel)?),\n        SqlTransform::Join { side, with, filter } => SqlTransform::Join {\n            side,\n            with: fold.fold_rel(with)?,\n            filter: fold.fold_expr(filter)?,\n        },\n\n        SqlTransform::Distinct => SqlTransform::Distinct,\n        SqlTransform::DistinctOn(ids) => SqlTransform::DistinctOn(fold.fold_cids(ids)?),\n        SqlTransform::Union { bottom, distinct } => SqlTransform::Union {\n            bottom: fold.fold_rel(bottom)?,\n            distinct,\n        },\n        SqlTransform::Except { bottom, distinct } => SqlTransform::Except {\n            bottom: fold.fold_rel(bottom)?,\n            distinct,\n        },\n        SqlTransform::Intersect { bottom, distinct } => SqlTransform::Intersect {\n            bottom: fold.fold_rel(bottom)?,\n            distinct,\n        },\n        SqlTransform::Select(v) => SqlTransform::Select(fold.fold_cids(v)?),\n        SqlTransform::Filter(v) => SqlTransform::Filter(fold.fold_expr(v)?),\n        SqlTransform::Aggregate { partition, compute } => SqlTransform::Aggregate {\n            partition: fold.fold_cids(partition)?,\n            compute: fold.fold_cids(compute)?,\n        },\n        SqlTransform::Sort(v) => SqlTransform::Sort(fold_column_sorts(fold, v)?),\n        SqlTransform::Take(take) => SqlTransform::Take(rq::Take {\n            partition: fold.fold_cids(take.partition)?,\n            sort: fold_column_sorts(fold, take.sort)?,\n            range: take.range,\n        }),\n    })\n}\n\npub trait PqFold: PqMapper<RelationExpr, RelationExpr, (), ()> {\n    fn fold_sql_query(&mut self, query: SqlQuery) -> Result<SqlQuery> {\n        Ok(SqlQuery {\n            ctes: query\n                .ctes\n                .into_iter()\n                .map(|c| self.fold_cte(c))\n                .try_collect()?,\n            main_relation: self.fold_sql_relation(query.main_relation)?,\n        })\n    }\n\n    fn fold_sql_relation(&mut self, relation: SqlRelation) -> Result<SqlRelation> {\n        Ok(match relation {\n            SqlRelation::AtomicPipeline(pipeline) => {\n                SqlRelation::AtomicPipeline(self.fold_sql_transforms(pipeline)?)\n            }\n            _ => relation,\n        })\n    }\n\n    fn fold_cte(&mut self, cte: Cte) -> Result<Cte> {\n        Ok(Cte {\n            tid: cte.tid,\n            kind: match cte.kind {\n                CteKind::Normal(rel) => CteKind::Normal(self.fold_sql_relation(rel)?),\n                CteKind::Loop { initial, step } => CteKind::Loop {\n                    initial: self.fold_sql_relation(initial)?,\n                    step: self.fold_sql_relation(step)?,\n                },\n            },\n        })\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/pq/context.rs",
    "content": "//! Transform the parsed AST into a \"materialized\" AST, by executing functions and\n//! replacing variables. The materialized AST is \"flat\", in the sense that it\n//! contains no query-specific logic.\nuse std::collections::HashMap;\nuse std::iter::zip;\n\nuse enum_as_inner::EnumAsInner;\nuse serde::Serialize;\n\nuse super::ast::{SqlRelation, SqlTransform};\nuse crate::ir::pl::Ident;\nuse crate::ir::rq::{\n    fold_table, CId, Compute, Relation, RelationColumn, RelationKind, RelationalQuery, RqFold, TId,\n    TableDecl, TableRef, Transform,\n};\nuse crate::sql::pq::positional_mapping::PositionalMapper;\nuse crate::utils::{IdGenerator, NameGenerator};\nuse crate::{ir::pl::TableExternRef::LocalTable, Result};\n\n/// The AnchorContext struct stores information about tables and columns, and\n/// is used to generate new IDs and names.\n#[derive(Default, Debug)]\npub struct AnchorContext {\n    pub column_decls: HashMap<CId, ColumnDecl>,\n    pub column_names: HashMap<CId, String>,\n\n    pub table_decls: HashMap<TId, SqlTableDecl>,\n\n    pub relation_instances: HashMap<RIId, RelationInstance>,\n\n    pub positional_mapping: PositionalMapper,\n\n    pub col_name: NameGenerator,\n    pub table_name: NameGenerator,\n\n    pub cid: IdGenerator<CId>,\n    pub tid: IdGenerator<TId>,\n    pub riid: IdGenerator<RIId>,\n}\n\n#[derive(Debug, Clone)]\npub struct SqlTableDecl {\n    #[allow(dead_code)]\n    pub id: TId,\n\n    /// Name of the table. Sometimes pull-in from RQ name hints (or database table names).\n    /// Generated in postprocessing.\n    pub name: Option<Ident>,\n\n    /// When set, any references to this decl will be redirected to the set TId.\n    pub redirect_to: Option<TId>,\n\n    /// Relation that still needs to be defined (usually as CTE) so it can be referenced by name.\n    /// None means that it has already been defined, or was not needed to be defined in the\n    /// first place.\n    pub relation: RelationStatus,\n}\n\n#[derive(Debug, Clone)]\npub enum RelationStatus {\n    /// Table or a common table expression. It can be referenced by name.\n    Defined,\n\n    /// Relation expression which is yet to be defined.\n    NotYetDefined(RelationAdapter),\n}\n\n#[derive(Debug)]\npub struct RelationInstance {\n    pub table_ref: TableRef,\n\n    /// When a pipeline is split, [CId]s from first pipeline are assigned a new\n    /// [CId] in the second pipeline.\n    pub cid_redirects: HashMap<CId, CId>,\n\n    /// All of cids pulled in when using a wildcard\n    pub original_cids: Vec<CId>,\n}\n\nimpl RelationStatus {\n    /// Analogous to [Option::take]\n    pub fn take_to_define(&mut self) -> RelationStatus {\n        std::mem::replace(self, RelationStatus::Defined)\n    }\n}\n\n/// A relation which may have already been preprocessed.\n#[derive(Debug, Clone)]\npub enum RelationAdapter {\n    Rq(Relation),\n    Preprocessed(Vec<SqlTransform>, Vec<RelationColumn>),\n    Pq(SqlRelation),\n}\n\nimpl From<SqlRelation> for RelationAdapter {\n    fn from(rel: SqlRelation) -> Self {\n        RelationAdapter::Pq(rel)\n    }\n}\n\nimpl From<Relation> for RelationAdapter {\n    fn from(rel: Relation) -> Self {\n        RelationAdapter::Rq(rel)\n    }\n}\n\n/// Table instance id\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]\npub struct RIId(usize);\n\nimpl From<usize> for RIId {\n    fn from(id: usize) -> Self {\n        RIId(id)\n    }\n}\n\n/// Column declaration.\n#[derive(Debug, PartialEq, Clone, strum::AsRefStr, EnumAsInner)]\npub enum ColumnDecl {\n    RelationColumn(RIId, CId, RelationColumn),\n    Compute(Box<Compute>),\n}\n\nimpl AnchorContext {\n    /// Returns a new AnchorContext object based on a Query object. This method\n    /// generates new IDs and names for tables and columns as needed.\n    pub fn of(query: RelationalQuery) -> (Self, Relation) {\n        let (cid, tid, query) = IdGenerator::load(query);\n\n        let context = AnchorContext {\n            cid,\n            tid,\n            riid: IdGenerator::new(),\n            col_name: NameGenerator::new(\"_expr_\"),\n            table_name: NameGenerator::new(\"table_\"),\n            ..Default::default()\n        };\n        QueryLoader::load(context, query)\n    }\n\n    // /// Generates a new ID and name for a wildcard column and registers it in the\n    // /// AnchorContext's column_decls HashMap.\n    // pub fn register_wildcard(&mut self, riid: RIId) -> CId {\n    //     let id = self.cid.gen();\n    //     let kind = ColumnDecl::RelationColumn(riid, id, RelationColumn::Wildcard);\n    //     self.column_decls.insert(id, kind);\n    //     id\n    // }\n\n    pub fn register_compute(&mut self, compute: Compute) {\n        let id = compute.id;\n        let decl = ColumnDecl::Compute(Box::new(compute));\n        self.column_decls.insert(id, decl);\n    }\n\n    /// Creates a new table instance and registers it in the AnchorContext's\n    /// table_instances HashMap. Also generates new IDs and names for columns\n    /// as needed.\n    pub fn create_relation_instance(\n        &mut self,\n        table_ref: TableRef,\n        cid_redirects: HashMap<CId, CId>,\n    ) -> RIId {\n        let riid = self.riid.gen();\n\n        for (col, cid) in &table_ref.columns {\n            let def = ColumnDecl::RelationColumn(riid, *cid, col.clone());\n            self.column_decls.insert(*cid, def);\n        }\n\n        let original_cids = table_ref.columns.iter().map(|(_, c)| *c).collect();\n        let relation_instance = RelationInstance {\n            table_ref,\n            cid_redirects,\n            original_cids,\n        };\n\n        self.relation_instances.insert(riid, relation_instance);\n        riid\n    }\n\n    /// Returns the name of a column if it has been given a name already, or generates\n    /// a new name for it and registers it in the AnchorContext's column_names HashMap.\n    pub(crate) fn ensure_column_name(&mut self, cid: CId) -> Option<&String> {\n        // don't name wildcards & named RelationColumns\n        let decl = &self.column_decls[&cid];\n        if let ColumnDecl::RelationColumn(_, _, col) = decl {\n            match col {\n                RelationColumn::Single(Some(name)) => {\n                    let entry = self.column_names.entry(cid);\n                    return Some(entry.or_insert_with(|| name.clone()));\n                }\n                RelationColumn::Wildcard => return None,\n                _ => {}\n            }\n        }\n\n        let entry = self.column_names.entry(cid);\n        Some(entry.or_insert_with(|| self.col_name.gen()))\n    }\n\n    pub(super) fn load_names(\n        &mut self,\n        pipeline: &[SqlTransform],\n        output_cols: Vec<RelationColumn>,\n    ) {\n        let output_cids = self.determine_select_columns(pipeline);\n\n        assert_eq!(output_cids.len(), output_cols.len());\n\n        for (cid, col) in zip(output_cids.iter(), output_cols) {\n            if let RelationColumn::Single(Some(name)) = col {\n                self.column_names.insert(*cid, name);\n            }\n        }\n    }\n\n    pub(super) fn determine_select_columns(&self, pipeline: &[SqlTransform]) -> Vec<CId> {\n        use SqlTransform::Super;\n\n        if let Some((last, remaining)) = pipeline.split_last() {\n            match last {\n                SqlTransform::From(table) => {\n                    let rel = self.relation_instances.get(table).unwrap();\n                    rel.table_ref.columns.iter().map(|(_, cid)| *cid).collect()\n                }\n                SqlTransform::Join { with, .. } => {\n                    let mut cols = self.determine_select_columns(remaining);\n\n                    let with = self.relation_instances.get(with).unwrap();\n                    let with = &with.table_ref.columns;\n                    cols.extend(with.iter().map(|(_, cid)| *cid));\n                    cols\n                }\n                Super(Transform::Select(cols)) => cols.clone(),\n                Super(Transform::Aggregate { partition, compute }) => {\n                    [partition.clone(), compute.clone()].concat()\n                }\n                _ => self.determine_select_columns(remaining),\n            }\n        } else {\n            Vec::new()\n        }\n    }\n\n    // /// Returns a set of all columns of all tables in a pipeline\n    // pub(super) fn collect_pipeline_inputs(\n    //     &self,\n    //     pipeline: &[SqlTransform],\n    // ) -> Result<(Vec<RIId>, HashSet<CId>)> {\n    //     let mut tables = Vec::new();\n    //     let mut columns = HashSet::new();\n    //     for t in pipeline {\n    //         if let SqlTransform::From(riid) | SqlTransform::Join { with: riid, .. } = t {\n    //             tables.push(*riid);\n\n    //             let rel = self.relation_instances.get(riid).unwrap();\n    //             columns.extend(rel.table_ref.columns.iter().map(|(_, cid)| cid));\n    //         }\n    //     }\n    //     Ok((tables, columns))\n    // }\n\n    pub(super) fn contains_wildcard(&self, cids: &[CId]) -> bool {\n        for cid in cids {\n            let decl = &self.column_decls[cid];\n            if let ColumnDecl::RelationColumn(_, _, RelationColumn::Wildcard) = decl {\n                return true;\n            }\n        }\n        false\n    }\n\n    pub fn lookup_table_decl(&self, tid: &TId) -> Option<&SqlTableDecl> {\n        let mut tid = tid;\n        loop {\n            let res = self.table_decls.get(tid)?;\n            match &res.redirect_to {\n                Some(redirect) => tid = redirect,\n                None => return Some(res),\n            }\n        }\n    }\n}\n\n/// Loads info about [Query] into [AnchorContext]\nstruct QueryLoader {\n    context: AnchorContext,\n}\n\nimpl QueryLoader {\n    fn load(context: AnchorContext, query: RelationalQuery) -> (AnchorContext, Relation) {\n        let mut loader = QueryLoader { context };\n\n        for t in query.tables {\n            loader.load_table(t).unwrap();\n        }\n        let relation = loader.fold_relation(query.relation).unwrap();\n        (loader.context, relation)\n    }\n\n    fn load_table(&mut self, table: TableDecl) -> Result<()> {\n        let decl = fold_table(self, table)?;\n        let mut name = decl.name.clone().map(Ident::from_name);\n\n        // assume name of the LocalTable that the relation is referencing\n        if let RelationKind::ExternRef(LocalTable(table)) = &decl.relation.kind {\n            name = Some(table.clone());\n        }\n\n        let sql_decl = SqlTableDecl {\n            id: decl.id,\n            name,\n            relation: if matches!(decl.relation.kind, RelationKind::ExternRef(_)) {\n                // this relation can be materialized by just using table name as a reference\n                // ... i.e. it's already defined.\n                RelationStatus::Defined\n            } else {\n                // this relation should be defined when needed\n                RelationStatus::NotYetDefined(decl.relation.into())\n            },\n            redirect_to: None,\n        };\n\n        self.context.table_decls.insert(decl.id, sql_decl);\n        Ok(())\n    }\n}\n\nimpl RqFold for QueryLoader {\n    fn fold_compute(&mut self, compute: Compute) -> Result<Compute> {\n        self.context.register_compute(compute.clone());\n        Ok(compute)\n    }\n\n    fn fold_table_ref(&mut self, table_ref: TableRef) -> Result<TableRef> {\n        Ok(table_ref)\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/pq/gen_query.rs",
    "content": "//! This module is responsible for translating RQ to PQ.\n\nuse std::str::FromStr;\n\nuse itertools::Itertools;\n\nuse super::super::{Context, Dialect};\nuse super::anchor::{self, anchor_split};\nuse super::ast::{self as pq, fold_sql_transform, PqMapper};\nuse super::context::{AnchorContext, RIId, RelationAdapter, RelationStatus};\nuse super::{postprocess, preprocess};\nuse crate::debug;\nuse crate::ir::rq::{self, RqFold};\nuse crate::utils::BreakUp;\nuse crate::{Result, Target};\n\npub(in super::super) fn compile_query(\n    query: rq::RelationalQuery,\n    dialect: Option<Dialect>,\n) -> Result<(pq::SqlQuery, Context)> {\n    debug::log_stage(debug::Stage::Sql(debug::StageSql::Anchor));\n\n    let dialect = if let Some(dialect) = dialect {\n        dialect\n    } else {\n        let target = query.def.other.get(\"target\");\n        let Target::Sql(maybe_dialect) = target\n            .map(|s| Target::from_str(s))\n            .transpose()?\n            .unwrap_or_default();\n        maybe_dialect.unwrap_or_default()\n    };\n\n    let (anchor, main_relation) = AnchorContext::of(query);\n\n    let mut ctx = Context::new(dialect, anchor);\n\n    // compile main relation that will recursively compile CTEs\n    let main_relation = compile_relation(main_relation.into(), &mut ctx)?;\n\n    // attach CTEs\n    let ctes = ctx.ctes.drain(..).collect_vec();\n\n    let query = pq::SqlQuery {\n        main_relation,\n        ctes,\n    };\n    debug::log_entry(|| debug::DebugEntryKind::ReprPq(query.clone()));\n\n    debug::log_stage(debug::Stage::Sql(debug::StageSql::Postprocess));\n    let query = postprocess::postprocess(query, &mut ctx);\n    debug::log_entry(|| debug::DebugEntryKind::ReprPq(query.clone()));\n\n    Ok((query, ctx))\n}\n\nfn compile_relation(relation: RelationAdapter, ctx: &mut Context) -> Result<pq::SqlRelation> {\n    log::trace!(\"compiling relation {relation:#?}\");\n\n    Ok(match relation {\n        RelationAdapter::Rq(rel) => {\n            match rel.kind {\n                // base case\n                rq::RelationKind::Pipeline(pipeline) => {\n                    // preprocess\n                    let pipeline = preprocess::preprocess(pipeline, ctx)?;\n\n                    // load names of output columns\n                    ctx.anchor.load_names(&pipeline, rel.columns);\n\n                    compile_pipeline(pipeline, ctx)?\n                }\n\n                rq::RelationKind::Literal(lit) => pq::SqlRelation::Literal(lit),\n                rq::RelationKind::SString(items) => pq::SqlRelation::SString(items),\n                rq::RelationKind::BuiltInFunction { name, args } => {\n                    pq::SqlRelation::Operator { name, args }\n                }\n\n                // ref cannot be converted directly into query and does not need it's own CTE\n                rq::RelationKind::ExternRef(_) => unreachable!(),\n            }\n        }\n\n        RelationAdapter::Preprocessed(pipeline, columns) => {\n            // load names of output columns\n            ctx.anchor.load_names(&pipeline, columns);\n\n            compile_pipeline(pipeline, ctx)?\n        }\n\n        RelationAdapter::Pq(rel) => rel,\n    })\n}\n\nfn compile_pipeline(\n    mut pipeline: Vec<pq::SqlTransform>,\n    ctx: &mut Context,\n) -> Result<pq::SqlRelation> {\n    use pq::SqlTransform::Super;\n\n    // special case: loop\n    if pipeline\n        .iter()\n        .any(|t| matches!(t, Super(rq::Transform::Loop(_))))\n    {\n        pipeline = compile_loop(pipeline, ctx)?;\n    }\n\n    // extract an atomic pipeline from back of the pipeline and stash preceding part into context\n    let pipeline = anchor::extract_atomic(pipeline, &mut ctx.anchor);\n\n    // ensure names for all columns that need it\n    ensure_names(&pipeline, &mut ctx.anchor);\n\n    log::trace!(\"compiling pipeline {pipeline:#?}\");\n\n    let mut c = TransformCompiler { ctx };\n    let pipeline = c.fold_sql_transforms(pipeline)?;\n\n    Ok(pq::SqlRelation::AtomicPipeline(pipeline))\n}\n\nstruct TransformCompiler<'a> {\n    ctx: &'a mut Context,\n}\n\nimpl RqFold for TransformCompiler<'_> {}\n\nimpl PqMapper<RIId, pq::RelationExpr, rq::Transform, ()> for TransformCompiler<'_> {\n    fn fold_rel(&mut self, rel: RIId) -> Result<pq::RelationExpr> {\n        compile_relation_instance(rel, self.ctx)\n    }\n\n    fn fold_super(&mut self, _: rq::Transform) -> Result<()> {\n        unreachable!()\n    }\n\n    fn fold_sql_transforms(\n        &mut self,\n        transforms: Vec<pq::SqlTransform<RIId, rq::Transform>>,\n    ) -> Result<Vec<pq::SqlTransform<pq::RelationExpr, ()>>> {\n        transforms\n            .into_iter()\n            .map(|transform| {\n                Ok(Some(match transform {\n                    pq::SqlTransform::From(v) => pq::SqlTransform::From(self.fold_rel(v)?),\n                    pq::SqlTransform::Join { side, with, filter } => pq::SqlTransform::Join {\n                        side,\n                        with: self.fold_rel(with)?,\n                        filter,\n                    },\n\n                    pq::SqlTransform::Super(sup) => {\n                        match sup {\n                            rq::Transform::Select(v) => pq::SqlTransform::Select(v),\n                            rq::Transform::Filter(v) => pq::SqlTransform::Filter(v),\n                            rq::Transform::Aggregate { partition, compute } => {\n                                pq::SqlTransform::Aggregate { partition, compute }\n                            }\n                            rq::Transform::Sort(v) => pq::SqlTransform::Sort(v),\n                            rq::Transform::Take(v) => pq::SqlTransform::Take(v),\n                            rq::Transform::Compute(_)\n                            | rq::Transform::Append(_)\n                            | rq::Transform::Loop(_) => {\n                                // these are not used from here on\n                                return Ok(None);\n                            }\n                            rq::Transform::From(_) | rq::Transform::Join { .. } => unreachable!(),\n                        }\n                    }\n                    _ => fold_sql_transform(self, transform)?,\n                }))\n            })\n            .flat_map(|x| x.transpose())\n            .try_collect()\n    }\n}\n\npub(super) fn compile_relation_instance(riid: RIId, ctx: &mut Context) -> Result<pq::RelationExpr> {\n    ctx.anchor.positional_mapping.activate_mapping(&riid);\n\n    let rel_instance = &ctx.anchor.relation_instances[&riid];\n    let nb_redirects = rel_instance.cid_redirects.len();\n    let table_ref = &rel_instance.table_ref;\n    let source = table_ref.source;\n    let decl = ctx.anchor.table_decls.get_mut(&table_ref.source).unwrap();\n\n    // ensure that the table is declared\n    if let RelationStatus::NotYetDefined(sql_relation) = decl.relation.take_to_define() {\n        // if we cannot use CTEs (probably because we are within RECURSIVE)\n        if !(ctx.query.allow_ctes && table_ref.prefer_cte) {\n            // restore relation for other references\n            decl.relation = RelationStatus::NotYetDefined(sql_relation.clone());\n\n            // return a sub-query\n            let relation = compile_relation(sql_relation, ctx)?;\n            return Ok(pq::RelationExpr {\n                kind: pq::RelationExprKind::SubQuery(relation),\n                riid,\n            });\n        }\n\n        let relation = compile_relation(sql_relation, ctx)?;\n\n        if let pq::SqlRelation::AtomicPipeline(pipeline) = &relation {\n            // Finding the last select statement of the pipeline\n            let last_select_columns = pipeline.iter().rev().find_map(|transform| match transform {\n                pq::SqlTransform::Select(cids) => Some(cids),\n                _ => None,\n            });\n\n            log::debug!(\"last select CIds for {riid:?}: {last_select_columns:?}\");\n\n            // If the pipeline ends with a select, we must recompute its CId redirects\n            if let Some(cids) = last_select_columns {\n                // Only recompute the CId redirects if there are exactly as many columns in the\n                // SELECT as there are CId redirects. This probably means that it is a projecting\n                // select added by `anchor_split`\n                if nb_redirects == cids.len() {\n                    log::debug!(\n                        \"recomputing cid_redirects for {riid:?}. current redirects: {:?}\",\n                        ctx.anchor.relation_instances[&riid].cid_redirects\n                    );\n                    // Inefficient but only way to ensure that the new redirects match the original cids\n                    let new_redirects = cids\n                        .iter()\n                        .zip(&ctx.anchor.relation_instances[&riid].original_cids)\n                        .map(|(new_cid, original_cid)| {\n                            let key_for_value = ctx.anchor.relation_instances[&riid]\n                                .cid_redirects\n                                .iter()\n                                .find_map(|(k, v)| if v == original_cid { Some(k) } else { None })\n                                .unwrap();\n\n                            (\n                                *new_cid,\n                                ctx.anchor.relation_instances[&riid].cid_redirects[key_for_value],\n                            )\n                        })\n                        .collect();\n\n                    log::debug!(\n                        \"recomputed cid_redirects for {riid:?}. new redirects: {new_redirects:?}\",\n                    );\n\n                    ctx.anchor\n                        .relation_instances\n                        .get_mut(&riid)\n                        .unwrap()\n                        .cid_redirects = new_redirects;\n                }\n            }\n        }\n        ctx.ctes.push(pq::Cte {\n            tid: source,\n            kind: pq::CteKind::Normal(relation),\n        });\n    }\n\n    Ok(pq::RelationExpr {\n        kind: pq::RelationExprKind::Ref(source),\n        riid,\n    })\n}\n\nfn compile_loop(\n    pipeline: Vec<pq::SqlTransform>,\n    ctx: &mut Context,\n) -> Result<Vec<pq::SqlTransform>> {\n    // split the pipeline\n    let (mut initial, mut following) =\n        pipeline.break_up(|t| matches!(t, pq::SqlTransform::Super(rq::Transform::Loop(_))));\n    let loop_ = following.remove(0);\n    let step = loop_.into_super_and(|t| t.into_loop()).unwrap();\n    let step = preprocess::preprocess(step, ctx)?;\n\n    // determine columns of the initial table\n    let recursive_columns = ctx.anchor.determine_select_columns(&initial);\n\n    // do the same thing we do when splitting a pipeline\n    // (defining new columns, redirecting cids)\n    let recursive_columns = pq::SqlTransform::Super(rq::Transform::Select(recursive_columns));\n    initial.push(recursive_columns.clone());\n    let step = anchor_split(&mut ctx.anchor, initial, step);\n    let from = step.first().unwrap().as_from().unwrap();\n    let from = ctx.anchor.relation_instances.get(from).unwrap();\n    let initial_tid = from.table_ref.source;\n\n    let initial = ctx.anchor.table_decls.get_mut(&initial_tid).unwrap();\n\n    // compile initial\n    let initial = if let RelationStatus::NotYetDefined(rel) = initial.relation.take_to_define() {\n        compile_relation(rel, ctx)?\n    } else {\n        unreachable!()\n    };\n\n    // compile step (without producing CTEs)\n    ctx.push_query();\n    ctx.query.allow_ctes = false;\n\n    let step = compile_pipeline(step, ctx)?;\n\n    ctx.pop_query();\n\n    // create a split between the loop SELECT statement and the following pipeline\n    let mut following = anchor_split(&mut ctx.anchor, vec![recursive_columns], following);\n\n    let from = following.first_mut().unwrap();\n    let from = from.as_from().unwrap();\n    let from = ctx.anchor.relation_instances.get(from).unwrap();\n    let from = &from.table_ref;\n\n    // this will be table decl that references the whole loop expression\n    let loop_decl = ctx.anchor.table_decls.get_mut(&from.source).unwrap();\n\n    loop_decl.redirect_to = Some(initial_tid);\n    loop_decl.relation = RelationStatus::Defined;\n\n    // push the whole thing into WITH of the main query\n    ctx.ctes.push(pq::Cte {\n        tid: from.source,\n        kind: pq::CteKind::Loop { initial, step },\n    });\n\n    Ok(following)\n}\n\nfn ensure_names(transforms: &[pq::SqlTransform], ctx: &mut AnchorContext) {\n    for t in transforms {\n        if let pq::SqlTransform::Super(rq::Transform::Sort(columns))\n        | pq::SqlTransform::Sort(columns) = t\n        {\n            for r in columns {\n                ctx.ensure_column_name(r.column);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/pq/mod.rs",
    "content": "//! Partitioned Query\n//!\n//! This in an internal intermediate representation between RQ and SQL AST.\n//!\n//! For example, RQ does not have a separate node for DISTINCT, but uses [crate::pr::rq::Take] 1 with\n//! `partition`. In [super::pq::preprocess] module, [crate::pr::rq::Transform] take is wrapped into\n//! [ast::SqlTransform], which does have [`ast::SqlTransform::Distinct`].\n//!\n//! This module also contains the compiler from RQ to PQ.\n\nmod anchor;\npub mod ast;\npub mod context;\nmod gen_query;\nmod positional_mapping;\nmod postprocess;\npub mod preprocess;\n\npub(super) use gen_query::compile_query;\n\n#[cfg(test)]\nmod test {\n    use super::ast::SqlQuery;\n    use super::*;\n    use crate::sql::Dialect;\n    use crate::{Errors, Result};\n\n    fn parse_and_resolve(source: &str) -> Result<SqlQuery, Errors> {\n        let query = crate::semantic::test::parse_resolve_and_lower(source)?;\n\n        let (sql, _) = compile_query(query, Some(Dialect::Generic))?;\n        Ok(sql)\n    }\n\n    fn count_atomics(prql: &str) -> Result<usize, Errors> {\n        let query = parse_and_resolve(prql)?;\n\n        Ok(query.ctes.len() + 1)\n    }\n\n    #[test]\n    fn test_ctes_of_pipeline() {\n        // One aggregate, take at the end\n        let prql: &str = r#\"\n        from employees\n        filter country == \"USA\"\n        aggregate {sal = average salary}\n        sort sal\n        take 20\n        \"#;\n\n        assert!(count_atomics(prql).unwrap() == 1);\n\n        // One aggregate, but take at the top\n        let prql: &str = r#\"\n        from employees\n        take 20\n        filter country == \"USA\"\n        aggregate {sal = average salary}\n        sort sal\n        \"#;\n\n        assert!(count_atomics(prql).unwrap() == 2);\n\n        // A take, then two aggregates\n        let prql: &str = r#\"\n        from employees\n        take 20\n        filter country == \"USA\"\n        aggregate {sal = average salary}\n        aggregate {sal2 = average sal}\n        sort sal2\n        \"#;\n\n        assert!(count_atomics(prql).unwrap() == 3);\n\n        // A take, then a select\n        let prql: &str = r###\"\n        from employees\n        take 20\n        select first_name\n        \"###;\n\n        assert!(count_atomics(prql).unwrap() == 1);\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/pq/positional_mapping.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::{\n    ir::rq::{CId, Compute, Transform},\n    sql::{\n        pq::{anchor::Requirements, context::RIId},\n        pq_ast::SqlTransform,\n    },\n};\n\n/// State required to properly handle the transforms that are order sensitive like `Union`.\n#[derive(Default, Debug)]\npub struct PositionalMapper {\n    pub relation_positional_mapping: HashMap<RIId, Vec<usize>>,\n    pub active_positional_mapping: Option<Vec<usize>>,\n}\n\nimpl PositionalMapper {\n    /// Remember the mapping for this `RIId` to know what to apply for `apply_positional_mapping`.\n    pub(crate) fn activate_mapping(&mut self, riid: &RIId) {\n        self.active_positional_mapping = self.relation_positional_mapping.remove(riid);\n        log::trace!(\n            \"loading remapping for {riid:?}: {:?}\",\n            self.active_positional_mapping\n        );\n    }\n\n    /// Reorder or remove columns to make `Union` happy.\n    pub(crate) fn apply_active_mapping(&mut self, output: Vec<CId>) -> Vec<CId> {\n        if let Some(mapping) = &self.active_positional_mapping {\n            // Check if the mapping indices are valid for the output\n            if mapping.iter().any(|idx| *idx >= output.len()) {\n                log::warn!(\n                    \"positional mapping indices out of bounds: mapping={mapping:?}, output_len={}\",\n                    output.len()\n                );\n                // If mapping is invalid, don't apply it\n                return output;\n            }\n\n            let new_output = mapping.iter().map(|idx| output[*idx]).collect();\n            log::debug!(\"remapping {output:?} to {new_output:?} via {mapping:?}\");\n            new_output\n        } else {\n            output\n        }\n    }\n\n    pub fn compute_and_store_mapping(&mut self, before: &[CId], after: &[CId], riid: &RIId) {\n        let mapping: Vec<_> = after\n            .iter()\n            .flat_map(|a| match before.iter().position(|b| b == a) {\n                Some(idx) => Some(idx),\n                None => {\n                    log::warn!(\".. no counterpart for column {a:?}\");\n                    None\n                }\n            })\n            .collect();\n\n        // Only store the mapping if it's complete (all columns matched)\n        // If mapping is incomplete, it means the bottom relation hasn't been fully\n        // compiled yet, so we shouldn't apply any mapping\n        if mapping.len() == after.len() && !self.relation_positional_mapping.contains_key(riid) {\n            log::debug!(\".. relation {riid:?} will be mapped: {mapping:?}\");\n            self.relation_positional_mapping.insert(*riid, mapping);\n        } else if mapping.len() != after.len() {\n            log::debug!(\n                \".. skipping incomplete mapping for {riid:?}: {}/{} columns matched\",\n                mapping.len(),\n                after.len()\n            );\n        }\n    }\n}\n\n/// Outputs the columns required for position sensitive transforms in the pipeline.\npub fn compute_positional_mappings(\n    pipeline: &[SqlTransform<RIId, Transform>],\n    requirements: Option<&Requirements>,\n) -> Vec<(RIId, Vec<CId>)> {\n    let mut constraints = vec![];\n    let mut columns = vec![];\n\n    log::trace!(\"traversing pipeline to obtain positional mapping:\");\n\n    // Only process selected columns to avoid surnumerary one\n    let add_columns = |columns: &mut Vec<CId>, cids: &[CId]| {\n        if let Some(requirements) = requirements {\n            columns.extend(cids.iter().filter(|cid| requirements.is_selected(cid)));\n        } else {\n            columns.extend_from_slice(cids);\n        }\n    };\n\n    for transform in pipeline {\n        match transform {\n            SqlTransform::Super(s) => match s {\n                Transform::Compute(Compute { id, .. }) => {\n                    if !columns.contains(id) {\n                        add_columns(&mut columns, &[*id]);\n                    }\n                }\n                Transform::Select(cids) => {\n                    columns.clear();\n                    add_columns(&mut columns, cids);\n                }\n                Transform::Aggregate { compute, .. } => {\n                    columns.clear();\n                    add_columns(&mut columns, compute);\n                }\n                _ => (),\n            },\n            SqlTransform::Except { bottom, .. }\n            | SqlTransform::Intersect { bottom, .. }\n            | SqlTransform::Union { bottom, .. } => {\n                constraints.push((*bottom, columns.clone()));\n                log::trace!(\n                    \".. mapping for {}/{bottom:?}: {columns:?}\",\n                    transform.as_str()\n                );\n            }\n            _ => (),\n        }\n        log::trace!(\n            \".. selected columns after {}: {columns:?}\",\n            transform.as_str()\n        );\n    }\n\n    constraints\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/pq/postprocess.rs",
    "content": "//! An AST pass after compilation to PQ.\n//!\n//! Currently only moves [SqlTransform::Sort]s.\n\nuse std::collections::{HashMap, HashSet, VecDeque};\n\nuse itertools::Itertools;\n\nuse super::anchor::CidRedirector;\nuse super::ast::*;\nuse crate::ir::generic::ColumnSort;\nuse crate::ir::pl::Ident;\nuse crate::ir::rq::{CId, ExprKind, RelationColumn, RqFold, TId};\nuse crate::sql::pq::context::{ColumnDecl, RIId};\nuse crate::sql::Context;\nuse crate::Result;\n\ntype Sorting = Vec<ColumnSort<CId>>;\n\npub(super) fn postprocess(query: SqlQuery, ctx: &mut Context) -> SqlQuery {\n    let query = infer_sorts(query, ctx);\n\n    assign_names(query, ctx)\n}\n\n/// Pushes sorts down the pipelines and materializes them only where they are needed.\nfn infer_sorts(query: SqlQuery, ctx: &mut Context) -> SqlQuery {\n    let mut s = SortingInference {\n        last_sorting: Vec::new(),\n        last_sorting_from_distinct_on: false,\n        ctes_sorting: HashMap::new(),\n        main_relation: false,\n        ctx,\n    };\n\n    s.fold_sql_query(query).unwrap()\n}\n\nstruct SortingInference<'a> {\n    last_sorting: Sorting,\n    /// True if last_sorting originated from DISTINCT ON (used for row selection).\n    ///\n    /// Per the PRQL spec, `group` resets the order - any `sort` inside a group\n    /// is for internal row selection, not output ordering. This flag tracks such\n    /// internal sorting so it doesn't propagate past transforms like `join`.\n    last_sorting_from_distinct_on: bool,\n    ctes_sorting: HashMap<TId, CteSorting>,\n    main_relation: bool,\n    ctx: &'a mut Context,\n}\n\nimpl SortingInference<'_> {\n    /// Prepares the last sorting that will be appended to the pipeline of the `SqlQuery` by\n    /// `fold_sql_query`. It does so by reverting all columns in the sorting to their very first\n    /// form, and then transforming their value in the final select, while applying\n    /// renaming/aliasing when possible. This cannot be done directly in `fold_sql_transforms`\n    /// because renames are not considered to be SQL transforms.\n    fn alias_last_sorting(&mut self, mut last_sorting: Sorting, final_select: &[CId]) -> Sorting {\n        log::debug!(\"unaliasing last sorting: {last_sorting:?}\");\n        let redirects = self\n            .ctx\n            .anchor\n            .relation_instances\n            .iter()\n            .map(|(riid, rel_inst)| (riid, &rel_inst.cid_redirects))\n            .collect::<HashMap<_, _>>();\n\n        // a map of column -> alias\n        let column_aliases = self\n            .ctx\n            .anchor\n            .column_decls\n            .values()\n            .filter_map(|col| {\n                if let ColumnDecl::Compute(compute) = col {\n                    if let ExprKind::ColumnRef(referenced_id) = compute.expr.kind {\n                        Some((referenced_id, compute.id))\n                    } else {\n                        None\n                    }\n                } else {\n                    None\n                }\n            })\n            .collect::<HashMap<_, _>>();\n        log::debug!(\".. column aliases: {column_aliases:?}\");\n\n        // column -> list of tables that did a revert\n        let mut reverts: HashMap<CId, VecDeque<RIId>> = HashMap::new();\n        log::debug!(\".. reverting all columns to their original value\");\n        last_sorting.iter_mut().for_each(|sort| {\n            let mut riids = VecDeque::new();\n            let mut changed = true;\n            while changed {\n                changed = false;\n                if let Some(ColumnDecl::RelationColumn(riid, cid, _)) =\n                    self.ctx.anchor.column_decls.get(&sort.column)\n                {\n                    let cid_redirects = redirects[riid];\n                    for (source, target) in cid_redirects.iter() {\n                        if target == cid {\n                            log::debug!(\n                                \".. reverting {target:?} back to {source:?} via redirects of {riid:?}\"\n                            );\n                            sort.column = *source;\n                            changed = true;\n                            riids.push_front(*riid);\n                            break;\n                        }\n                    }\n                }\n            }\n            reverts.insert(sort.column, riids);\n        });\n        log::debug!(\".. done reverting all columns to their original value: {last_sorting:?}\");\n\n        log::debug!(\".. reverting columns forward and aliasing them\");\n        // reverting forward\n        last_sorting.iter_mut().for_each(|sort| {\n            let col_reverts = &reverts[&sort.column];\n            for riid in col_reverts {\n                if final_select.contains(&sort.column) {\n                    log::debug!(\n                        \".. sort column {:?} is in the final select columns, skip reverting\",\n                        &sort.column\n                    );\n                    return;\n                }\n                // try renaming\n                if column_aliases.contains_key(&sort.column) {\n                    let alias = column_aliases[&sort.column];\n                    log::debug!(\"..aliasing {:?} as {alias:?}\", &sort.column);\n                    sort.column = alias;\n                }\n                // try de-reverting with the target table\n                let cid_mappings = redirects[riid];\n                if cid_mappings.contains_key(&sort.column) {\n                    log::debug!(\n                        \".. reverting {:?} forward to {:?} via redirects of {riid:?} ({:?})\",\n                        &sort.column,\n                        &cid_mappings[&sort.column],\n                        &cid_mappings\n                    );\n                    sort.column = cid_mappings[&sort.column];\n                }\n            }\n        });\n\n        log::debug!(\"aliased and reverted last sorting forward: {last_sorting:?}\");\n\n        last_sorting\n    }\n}\n\n#[derive(Debug)]\nstruct CteSorting {\n    sorting: Sorting,\n    /// True if the CTE's sorting originated from DISTINCT ON (row selection).\n    ///\n    /// Per the PRQL spec, `group` resets the order. DISTINCT ON sorting is\n    /// internal to the group - it determines which row to keep, not output\n    /// ordering. This flag ensures such sorting doesn't leak to outer queries.\n    from_distinct_on: bool,\n}\n\nimpl RqFold for SortingInference<'_> {}\n\n/// Returns the CIds for the last SELECT statement in an AtomicPipeline CTE, or None if no such\n/// statement is found.\nfn find_last_select_for_cte(cte: &Cte) -> Option<&Vec<CId>> {\n    match &cte.kind {\n        CteKind::Normal(SqlRelation::AtomicPipeline(pipeline)) => {\n            pipeline.iter().rev().find_map(|transform| {\n                if let SqlTransform::Select(cids) = transform {\n                    Some(cids)\n                } else {\n                    None\n                }\n            })\n        }\n        _ => None,\n    }\n}\n\nimpl PqFold for SortingInference<'_> {\n    fn fold_sql_query(&mut self, query: SqlQuery) -> Result<SqlQuery> {\n        let mut ctes = Vec::with_capacity(query.ctes.len());\n\n        for cte in query.ctes {\n            log::debug!(\"infer_sorts: {0:?}\", cte.tid);\n\n            let last_select_before_fold = find_last_select_for_cte(&cte).cloned();\n            let cte = self.fold_cte(cte)?;\n            let last_select_after_fold = find_last_select_for_cte(&cte);\n\n            // Checking if CTE folding has added some new columns to the CTE\n            match (last_select_before_fold.as_ref(), last_select_after_fold) {\n                (Some(before), Some(after)) if before != after => {\n                    let new_columns = after\n                        .iter()\n                        .filter(|cid| !before.contains(cid))\n                        .collect::<Vec<_>>();\n\n                    // if new columns are added, the relation instance needs to be updated\n                    if !new_columns.is_empty() {\n                        let mut cid_redirects_to_add = Vec::new();\n                        for old_cid in new_columns {\n                            let new_cid = self.ctx.anchor.cid.gen();\n                            let name = self.ctx.anchor.ensure_column_name(*old_cid).cloned();\n                            if let Some(name) = name.clone() {\n                                self.ctx.anchor.column_names.insert(new_cid, name);\n                            }\n\n                            let old_def = self.ctx.anchor.column_decls.get(old_cid).unwrap();\n                            let col = match old_def {\n                                ColumnDecl::RelationColumn(_, _, RelationColumn::Wildcard) => {\n                                    RelationColumn::Wildcard\n                                }\n                                _ => RelationColumn::Single(name),\n                            };\n\n                            cid_redirects_to_add.push((*old_cid, new_cid, col));\n                        }\n\n                        let (riid, relation_instance) = self\n                            .ctx\n                            .anchor\n                            .relation_instances\n                            .iter_mut()\n                            .find(|(_riid, rel_inst)| rel_inst.table_ref.source == cte.tid)\n                            .unwrap();\n\n                        cid_redirects_to_add\n                            .into_iter()\n                            .for_each(|(old_cid, new_cid, col)| {\n                                let def = ColumnDecl::RelationColumn(*riid, new_cid, col);\n\n                                self.ctx.anchor.column_decls.insert(new_cid, def);\n                                log::debug!(\n                                    \"-- redirecting {old_cid:?} to {new_cid:?} for CTE {cte:?} (RIId: {riid:?})\",\n                                    cte = cte.tid,\n                                    riid = riid\n                                );\n\n                                relation_instance.cid_redirects.insert(old_cid, new_cid);\n                            });\n                    }\n                }\n                _ => {}\n            }\n\n            // store sorting to be used later in From references\n            let sorting = self.last_sorting.drain(..).collect();\n            log::debug!(\"--- sorting {sorting:?}\");\n            let sorting = CteSorting {\n                sorting,\n                from_distinct_on: self.last_sorting_from_distinct_on,\n            };\n            self.last_sorting_from_distinct_on = false;\n            self.ctes_sorting.insert(cte.tid, sorting);\n\n            ctes.push(cte);\n        }\n\n        // fold main_relation\n        log::debug!(\"infer_sorts: main relation\");\n        self.main_relation = true;\n        let mut main_relation = self.fold_sql_relation(query.main_relation)?;\n        log::debug!(\"--== last_sorting {0:?}\", self.last_sorting);\n        let last_sorting = self.last_sorting.drain(..).collect::<Vec<_>>();\n\n        // push a sort at the back of the main pipeline\n        if let SqlRelation::AtomicPipeline(pipeline) = &mut main_relation {\n            let from_id = pipeline\n                .iter()\n                .find_map(|transform| match transform {\n                    SqlTransform::From(rel) => Some(rel.riid),\n                    _ => None,\n                })\n                .unwrap();\n\n            let final_select = pipeline\n                .iter()\n                .rev()\n                .find_map(|transform| match transform {\n                    SqlTransform::Select(select) => Some(select),\n                    _ => None,\n                })\n                .unwrap();\n            log::debug!(\"--== final select: {final_select:?}\");\n\n            let unaliased_last_sorting = self.alias_last_sorting(last_sorting, final_select);\n            log::debug!(\"--== unaliased last sorting: {unaliased_last_sorting:?}\");\n            let redirected_last_sorting = CidRedirector::redirect_sorts(\n                unaliased_last_sorting,\n                &from_id,\n                &mut self.ctx.anchor,\n            );\n            log::debug!(\"--== redirected last sorting: {redirected_last_sorting:?}\");\n\n            pipeline.push(SqlTransform::Sort(redirected_last_sorting));\n        }\n\n        Ok(SqlQuery {\n            ctes,\n            main_relation,\n        })\n    }\n}\n\nimpl PqMapper<RelationExpr, RelationExpr, (), ()> for SortingInference<'_> {\n    fn fold_rel(&mut self, rel: RelationExpr) -> Result<RelationExpr> {\n        Ok(rel)\n    }\n\n    fn fold_super(&mut self, sup: ()) -> Result<()> {\n        Ok(sup)\n    }\n\n    fn fold_sql_transforms(\n        &mut self,\n        transforms: Vec<SqlTransform<RelationExpr, ()>>,\n    ) -> Result<Vec<SqlTransform<RelationExpr, ()>>> {\n        let mut sorting = Vec::new();\n        // Track whether sorting originated from DISTINCT ON (internal row selection).\n        // Per PRQL spec, `group` resets order - internal sorts don't define output order.\n        let mut sorting_from_distinct_on = false;\n\n        let mut result = Vec::with_capacity(transforms.len() + 1);\n\n        for mut transform in transforms {\n            match transform {\n                SqlTransform::From(mut expr) => {\n                    match expr.kind {\n                        RelationExprKind::Ref(ref tid) => {\n                            // infer sorting from referenced pipeline\n                            if let Some(cte_sorting) = self.ctes_sorting.get(tid) {\n                                sorting.clone_from(&cte_sorting.sorting);\n                                sorting_from_distinct_on = cte_sorting.from_distinct_on;\n                            } else {\n                                sorting.clear();\n                                sorting_from_distinct_on = false;\n                            };\n                        }\n                        RelationExprKind::SubQuery(rel) => {\n                            let rel = self.fold_sql_relation(rel)?;\n\n                            // infer sorting from sub-query\n                            sorting = self.last_sorting.drain(..).collect();\n                            sorting_from_distinct_on = self.last_sorting_from_distinct_on;\n\n                            expr.kind = RelationExprKind::SubQuery(rel);\n                        }\n                    }\n                    sorting =\n                        CidRedirector::redirect_sorts(sorting, &expr.riid, &mut self.ctx.anchor);\n                    transform = SqlTransform::From(expr);\n                }\n\n                // just store sorting and don't emit Sort\n                SqlTransform::Sort(expr) => {\n                    sorting.clone_from(&expr);\n                    // A new explicit Sort clears the DISTINCT ON flag - this is a\n                    // user-requested ordering, not an internal DISTINCT ON sort.\n                    sorting_from_distinct_on = false;\n                    continue;\n                }\n\n                // clear sorting\n                SqlTransform::Distinct | SqlTransform::Aggregate { .. } => {\n                    sorting.clear();\n                    sorting_from_distinct_on = false;\n                }\n\n                // Per PRQL spec: `group` resets order, `join` retains left's order.\n                // DISTINCT ON sorting is internal to the group (for row selection),\n                // so it must not propagate past joins. Explicit user sorts are preserved.\n                // See issue #4633.\n                SqlTransform::Join { .. } => {\n                    if sorting_from_distinct_on {\n                        sorting.clear();\n                        sorting_from_distinct_on = false;\n                    }\n                }\n\n                // emit Sort before Take\n                SqlTransform::Take(ref take) => {\n                    // For simple takes (no partition), the sort may be embedded in the Take\n                    // struct from RQ lowering (sort | take -> Take { sort: [...] }).\n                    // Use the embedded sort if present, otherwise use accumulated sorting.\n                    //\n                    // Invariant: For simple takes, only one of take.sort or sorting should\n                    // be non-empty, never both. RQ lowering merges `sort | take` into a\n                    // single Take with embedded sort, while explicit SqlTransform::Sort\n                    // only appears when sort is not immediately followed by take.\n                    let sort_to_emit = if take.partition.is_empty() && !take.sort.is_empty() {\n                        take.sort.clone()\n                    } else {\n                        sorting.clone()\n                    };\n                    result.push(SqlTransform::Sort(sort_to_emit));\n                }\n\n                SqlTransform::DistinctOn(_) => {\n                    // DISTINCT ON uses sorting for row selection within the group.\n                    // Mark it so this internal sorting doesn't leak to outer queries.\n                    // Note: ROW_NUMBER (used for take > 1 or non-Postgres) doesn't have\n                    // this issue because its sorting is embedded in the window function.\n                    sorting_from_distinct_on = true;\n                    result.push(SqlTransform::Sort(sorting.clone()));\n                }\n                _ => {}\n            }\n            result.push(transform)\n        }\n        log::debug!(\"-- relation sorting {sorting:?}\");\n\n        if !self.main_relation {\n            // if this is a CTE, make sure that its SELECT includes the\n            // columns from the sort\n            let select = result.iter_mut().find_map(|x| x.as_select_mut()).unwrap();\n            for column_sort in &sorting {\n                let cid = column_sort.column;\n                let is_selected = select.contains(&cid);\n                if !is_selected {\n                    log::debug!(\"adding {cid:?} to {select:?}\");\n                    select.push(cid);\n                }\n            }\n        }\n\n        // remember sorting for this pipeline\n        self.last_sorting = sorting;\n        self.last_sorting_from_distinct_on = sorting_from_distinct_on;\n\n        Ok(result)\n    }\n}\n\n/// Makes sure all relation instances have assigned names. Tries to infer from table references.\nfn assign_names(query: SqlQuery, ctx: &mut Context) -> SqlQuery {\n    // generate CTE names, make sure they don't clash\n    let decls = ctx.anchor.table_decls.values_mut();\n    let mut names = HashSet::new();\n    for decl in decls.sorted_by_key(|d| d.id.get()) {\n        while decl.name.is_none() || names.contains(decl.name.as_ref().unwrap()) {\n            decl.name = Some(Ident::from_name(ctx.anchor.table_name.gen()));\n        }\n        names.insert(decl.name.clone().unwrap());\n    }\n\n    // generate relation variable names\n    RelVarNameAssigner {\n        ctx,\n        relation_instance_names: Default::default(),\n    }\n    .fold_sql_query(query)\n    .unwrap()\n}\n\nstruct RelVarNameAssigner<'a> {\n    relation_instance_names: HashSet<String>,\n\n    ctx: &'a mut Context,\n}\n\nimpl RqFold for RelVarNameAssigner<'_> {}\n\nimpl PqFold for RelVarNameAssigner<'_> {\n    fn fold_sql_relation(&mut self, relation: SqlRelation) -> Result<SqlRelation> {\n        // only fold AtomicPipelines\n        Ok(match relation {\n            SqlRelation::AtomicPipeline(pipeline) => {\n                // save outer names, so they are not affected by the inner pipeline\n                // (this matters for loop, where you have nested pipelines)\n                let outer_names = std::mem::take(&mut self.relation_instance_names);\n\n                let res = self.fold_sql_transforms(pipeline)?;\n\n                self.relation_instance_names = outer_names;\n                SqlRelation::AtomicPipeline(res)\n            }\n            _ => relation,\n        })\n    }\n}\n\nimpl PqMapper<RelationExpr, RelationExpr, (), ()> for RelVarNameAssigner<'_> {\n    fn fold_rel(&mut self, mut rel: RelationExpr) -> Result<RelationExpr> {\n        // normal fold\n        rel.kind = match rel.kind {\n            RelationExprKind::Ref(tid) => RelationExprKind::Ref(tid),\n            RelationExprKind::SubQuery(sub) => {\n                RelationExprKind::SubQuery(self.fold_sql_relation(sub)?)\n            }\n        };\n\n        // make sure that table_ref has a name\n        let riid = &rel.riid;\n        let instance = self.ctx.anchor.relation_instances.get_mut(riid).unwrap();\n        let name = &mut instance.table_ref.name;\n\n        if name.is_none() {\n            // it does not\n\n            // infer from table name\n            *name = match &rel.kind {\n                RelationExprKind::Ref(tid) => {\n                    let table_decl = &self.ctx.anchor.table_decls[tid];\n                    table_decl.name.as_ref().map(|i| i.name.clone())\n                }\n                _ => None,\n            };\n        }\n\n        // make sure it is not already present in current query\n        while name\n            .as_ref()\n            .map_or(true, |n| self.relation_instance_names.contains(n))\n        {\n            *name = Some(self.ctx.anchor.table_name.gen());\n        }\n\n        // mark name as used\n        self.relation_instance_names.insert(name.clone().unwrap());\n\n        Ok(rel)\n    }\n\n    fn fold_super(&mut self, sup: ()) -> Result<()> {\n        Ok(sup)\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/pq/preprocess.rs",
    "content": "use std::collections::hash_map::RandomState;\nuse std::collections::{HashMap, HashSet};\n\nuse itertools::Itertools;\n\nuse super::anchor::{infer_complexity, CidCollector, Complexity};\nuse super::ast::*;\n\nuse crate::ir::generic::{ColumnSort, SortDirection, WindowFrame, WindowKind};\nuse crate::ir::pl::{JoinSide, Literal};\nuse crate::ir::rq::{\n    self, maybe_binop, new_binop, CId, Compute, Expr, ExprKind, RqFold, Transform, Window,\n};\nuse crate::sql::Context;\nuse crate::{debug, Error, Result, WithErrorInfo};\nuse prqlc_parser::generic::{InterpolateItem, Range};\n\n/// Converts RQ AST into SqlRQ AST and applies a few preprocessing operations.\n///\n/// Note that some SQL translation mechanisms depend on behavior of some of these\n/// functions (i.e. reorder).\npub(in crate::sql) fn preprocess(\n    pipeline: Vec<Transform>,\n    ctx: &mut Context,\n) -> Result<Vec<SqlTransform>> {\n    Ok(pipeline)\n        .and_then(normalize)\n        .and_then(|p| wrap(p, ctx))\n        .and_then(|p| prune_inputs(p, ctx))\n        .and_then(|p| distinct(p, ctx))\n        .and_then(|p| union(p, ctx))\n        .and_then(|p| except(p, ctx))\n        .and_then(|p| intersect(p, ctx))\n        .map(reorder)\n        .map(|p| {\n            debug::log_entry(|| debug::DebugEntryKind::ReprPqEarly(p.clone()));\n            p\n        })\n}\n\n// This function was disabled because it changes semantics of the pipeline in some cases.\n// /// Pushes all [Transform::Select]s to the back of the pipeline.\n// pub(in crate::sql) fn push_down_selects(pipeline: Vec<Transform>) -> Vec<Transform> {\n//     let mut select = None;\n//     let mut res = Vec::with_capacity(pipeline.len());\n//     for t in pipeline {\n//         if let Transform::Select(_) = t {\n//             select = Some(t);\n//         } else {\n//             res.push(t);\n//         }\n//     }\n//     if let Some(select) = select {\n//         res.push(select);\n//     }\n//     res\n// }\n\n/// Removes unused relation inputs\npub(in crate::sql) fn prune_inputs(\n    mut pipeline: Vec<SqlTransform>,\n    ctx: &mut Context,\n) -> Result<Vec<SqlTransform>> {\n    use SqlTransform::Super;\n\n    let mut used_cids = HashSet::new();\n\n    let mut res = Vec::new();\n    while let Some(mut transform) = pipeline.pop() {\n        // collect cids (special case for Join & From)\n        match transform {\n            SqlTransform::Join { ref filter, .. } => {\n                used_cids.extend(CidCollector::collect(filter.clone()));\n            }\n            SqlTransform::From(_) => {}\n            Super(t) => {\n                let (t, cids) = CidCollector::collect_t(t);\n                used_cids.extend(cids);\n                transform = Super(t);\n            }\n            _ => unreachable!(),\n        }\n\n        // prune unused inputs\n        if let SqlTransform::From(with) | SqlTransform::Join { with, .. } = &mut transform {\n            let relation = ctx.anchor.relation_instances.get_mut(with).unwrap();\n            (relation.table_ref.columns).retain(|(_, cid)| used_cids.contains(cid));\n        }\n\n        res.push(transform);\n    }\n\n    res.reverse();\n    Ok(res)\n}\n\npub(in crate::sql) fn wrap(pipe: Vec<Transform>, ctx: &mut Context) -> Result<Vec<SqlTransform>> {\n    // We map From and Join into SqlTransforms, because we need to change their RIIds.\n    // Others we just wrap into SqlTransform::Super.\n\n    pipe.into_iter()\n        .map(|x| {\n            Ok(match x {\n                Transform::From(table_ref) => {\n                    let riid = ctx\n                        .anchor\n                        .create_relation_instance(table_ref, HashMap::new());\n                    SqlTransform::From(riid)\n                }\n                Transform::Join { with, side, filter } => {\n                    let with = ctx.anchor.create_relation_instance(with, HashMap::new());\n                    SqlTransform::Join { with, side, filter }\n                }\n                x => SqlTransform::Super(x),\n            })\n        })\n        .try_collect()\n}\n\nfn vecs_contain_same_elements<T: Eq + std::hash::Hash>(a: &[T], b: &[T]) -> bool {\n    let a: HashSet<&T, RandomState> = a.iter().collect();\n    let b: HashSet<&T, RandomState> = b.iter().collect();\n    a == b\n}\n\n/// Creates [SqlTransform::Distinct] from [Transform::Take]\npub(in crate::sql) fn distinct(\n    pipeline: Vec<SqlTransform>,\n    ctx: &mut Context,\n) -> Result<Vec<SqlTransform>> {\n    use SqlTransform::Super;\n    use Transform::*;\n\n    let mut res = Vec::new();\n    for transform in pipeline.clone() {\n        match transform {\n            Super(Take(rq::Take { ref partition, .. })) if partition.is_empty() => {\n                res.push(transform);\n            }\n\n            Super(Take(rq::Take {\n                range,\n                partition,\n                sort,\n            })) => {\n                let range_int = range\n                    .clone()\n                    .try_map(as_int)\n                    .map_err(|_| Error::new_simple(\"Invalid take arguments\"))?;\n\n                let take_only_first =\n                    range_int.start.unwrap_or(1) == 1 && matches!(range_int.end, Some(1));\n\n                // Check whether the columns within the partition are the same\n                // as the columns in the table; otherwise we can't use DISTINCT.\n                let columns_in_frame = ctx.anchor.determine_select_columns(&pipeline.clone());\n                let matching_columns = vecs_contain_same_elements(&columns_in_frame, &partition);\n\n                if take_only_first && sort.is_empty() && matching_columns {\n                    // DISTINCT\n\n                    res.push(SqlTransform::Distinct);\n                } else if ctx.dialect.supports_distinct_on() && range_int.end == Some(1) {\n                    // DISTINCT ON (only if we want to select only one row per group)\n\n                    let sort = if sort.is_empty() {\n                        vec![]\n                    } else {\n                        [into_column_sort(&partition), sort].concat()\n                    };\n\n                    res.push(SqlTransform::Sort(sort));\n                    res.push(SqlTransform::DistinctOn(partition));\n                } else {\n                    // convert `take range` into:\n                    //   derive _rn = s\"ROW NUMBER\"\n                    //   filter (_rn | in range)\n                    res.extend(create_filter_by_row_number(range, sort, partition, ctx));\n                }\n            }\n            _ => {\n                res.push(transform);\n            }\n        }\n    }\n    Ok(res)\n}\n\nfn into_column_sort(partition: &[CId]) -> Vec<ColumnSort<CId>> {\n    partition\n        .iter()\n        .map(|cid| ColumnSort {\n            direction: SortDirection::Asc,\n            column: *cid,\n        })\n        .collect_vec()\n}\n\nfn create_filter_by_row_number(\n    range: Range<Expr>,\n    sort: Vec<ColumnSort<CId>>,\n    partition: Vec<CId>,\n    ctx: &mut Context,\n) -> Vec<SqlTransform> {\n    // declare new column\n    let expr = Expr {\n        kind: ExprKind::SString(vec![InterpolateItem::String(\"ROW_NUMBER()\".to_string())]),\n        span: None,\n    };\n\n    let is_unsorted = sort.is_empty();\n    let window = Window {\n        frame: if is_unsorted {\n            WindowFrame {\n                kind: WindowKind::Rows,\n                range: Range::unbounded(),\n            }\n        } else {\n            WindowFrame {\n                kind: WindowKind::Range,\n                range: Range {\n                    start: None,\n                    end: Some(int_expr(0)),\n                },\n            }\n        },\n        partition,\n        sort,\n    };\n\n    let compute = Compute {\n        id: ctx.anchor.cid.gen(),\n        expr,\n        window: Some(window),\n        is_aggregation: false,\n    };\n\n    ctx.anchor.register_compute(compute.clone());\n\n    let col_ref = Expr {\n        kind: ExprKind::ColumnRef(compute.id),\n        span: None,\n    };\n\n    // add the two transforms\n    let range_int = range.try_map(as_int).unwrap();\n\n    let compute = SqlTransform::Super(Transform::Compute(compute));\n    let filter = SqlTransform::Super(Transform::Filter(match (range_int.start, range_int.end) {\n        (Some(s), Some(e)) if s == e => new_binop(col_ref, \"std.eq\", int_expr(s)),\n        (start, end) => {\n            let start = start.map(|start| new_binop(col_ref.clone(), \"std.gte\", int_expr(start)));\n            let end = end.map(|end| new_binop(col_ref, \"std.lte\", int_expr(end)));\n\n            maybe_binop(start, \"std.and\", end).unwrap_or(Expr {\n                kind: ExprKind::Literal(Literal::Boolean(true)),\n                span: None,\n            })\n        }\n    }));\n\n    vec![compute, filter]\n}\n\nfn as_int(expr: Expr) -> Result<i64, ()> {\n    let lit = expr.kind.as_literal().ok_or(())?;\n    lit.as_integer().cloned().ok_or(())\n}\n\nfn int_expr(i: i64) -> Expr {\n    Expr {\n        span: None,\n        kind: ExprKind::Literal(Literal::Integer(i)),\n    }\n}\n\n/// Creates [SqlTransform::Union] from [Transform::Append]\npub(in crate::sql) fn union(\n    pipeline: Vec<SqlTransform>,\n    ctx: &mut Context,\n) -> Result<Vec<SqlTransform>> {\n    use SqlTransform::*;\n    use Transform::*;\n\n    let mut res = Vec::with_capacity(pipeline.len());\n    let mut pipeline = pipeline.into_iter().peekable();\n    while let Some(t) = pipeline.next() {\n        let Super(Append(bottom)) = t else {\n            res.push(t);\n            continue;\n        };\n        let bottom = ctx.anchor.create_relation_instance(bottom, HashMap::new());\n\n        let distinct = if let Some(Distinct) = &pipeline.peek() {\n            pipeline.next();\n            true\n        } else {\n            false\n        };\n\n        res.push(SqlTransform::Union { bottom, distinct });\n    }\n    Ok(res)\n}\n\n/// Creates [SqlTransform::Except] from [Transform::Join] and [Transform::Filter]\npub(in crate::sql) fn except(\n    pipeline: Vec<SqlTransform>,\n    ctx: &mut Context,\n) -> Result<Vec<SqlTransform>> {\n    use SqlTransform::*;\n\n    let output = ctx.anchor.determine_select_columns(&pipeline);\n    let output: HashSet<CId, RandomState> = HashSet::from_iter(output);\n\n    let mut res = Vec::with_capacity(pipeline.len());\n    for t in pipeline {\n        res.push(t);\n\n        if res.len() < 2 {\n            continue;\n        }\n        let SqlTransform::Join {\n            side: JoinSide::Left,\n            filter: join_cond,\n            with,\n        } = &res[res.len() - 2]\n        else {\n            continue;\n        };\n        let Super(Transform::Filter(filter)) = &res[res.len() - 1] else {\n            continue;\n        };\n\n        let with = ctx.anchor.relation_instances.get(with).unwrap();\n\n        let top = ctx.anchor.determine_select_columns(&res[0..res.len() - 2]);\n        let bottom = with.table_ref.columns.iter().map(|(_, c)| *c).collect_vec();\n\n        // join_cond must be a join over all columns\n        // (this could be loosened to check only the relation key)\n        let (join_left, join_right) = collect_equals(join_cond)?;\n        if !all_in(&top, join_left) || !all_in(&bottom, join_right) {\n            continue;\n        }\n\n        // filter has to check for nullability of bottom\n        // (this could be loosened to check only for nulls in a previously non-nullable column)\n        let (filter_left, filter_right) = collect_equals(filter)?;\n        if !(all_in(&bottom, filter_left) && all_null(filter_right)) {\n            continue;\n        }\n\n        // select must not contain things from bottom\n        if bottom.iter().any(|c| output.contains(c)) {\n            continue;\n        }\n\n        // determine DISTINCT\n        let mut distinct = false;\n        // EXCEPT ALL can become except EXCEPT DISTINCT, if top is DISTINCT.\n        // DISTINCT-ness of bottom has no effect on the output.\n        if res.len() >= 3 {\n            if let Distinct = &res[res.len() - 3] {\n                distinct = true;\n            }\n        }\n\n        if !distinct && !ctx.dialect.except_all() {\n            // EXCEPT ALL is not supported\n            // can we fall back to anti-join?\n            if ctx.anchor.contains_wildcard(&top) || ctx.anchor.contains_wildcard(&bottom) {\n                return Err(Error::new_simple(format!(\"The dialect {:?} does not support EXCEPT ALL\", ctx.dialect))\n                    .push_hint(\"providing more column information will allow the query to be translated to an anti-join.\"));\n            } else {\n                // Don't create Except, fallback to anti-join.\n                continue;\n            }\n        }\n\n        res.pop(); // filter\n        let join = res.pop(); // join\n        let (_, with, _) = join.unwrap().into_join().unwrap();\n        if distinct {\n            if let Some(Distinct) = &res.last() {\n                res.pop();\n            }\n        }\n\n        res.push(SqlTransform::Except {\n            bottom: with,\n            distinct,\n        });\n    }\n\n    Ok(res)\n}\n\n/// Creates [SqlTransform::Intersect] from [Transform::Join]\npub(in crate::sql) fn intersect(\n    pipeline: Vec<SqlTransform>,\n    ctx: &mut Context,\n) -> Result<Vec<SqlTransform>> {\n    use SqlTransform::*;\n\n    let output = ctx.anchor.determine_select_columns(&pipeline);\n    let output: HashSet<CId, RandomState> = HashSet::from_iter(output);\n\n    let mut res = Vec::with_capacity(pipeline.len());\n    let mut pipeline = pipeline.into_iter().peekable();\n    while let Some(t) = pipeline.next() {\n        res.push(t);\n\n        if res.is_empty() {\n            continue;\n        }\n        let Join {\n            side: JoinSide::Inner,\n            filter: join_cond,\n            with,\n        } = &res[res.len() - 1]\n        else {\n            continue;\n        };\n        let with = ctx.anchor.relation_instances.get_mut(with).unwrap();\n\n        let bottom = with.table_ref.columns.iter().map(|(_, c)| *c).collect_vec();\n        let top = ctx.anchor.determine_select_columns(&res[0..res.len() - 1]);\n\n        // join_cond must be a join over all columns\n        // (this could be loosened to check only the relation key)\n        let (left, right) = collect_equals(join_cond)?;\n        if !(all_in(&top, left) && all_in(&bottom, right)) {\n            continue;\n        }\n\n        // select must not contain things from bottom\n        if bottom.iter().any(|c| output.contains(c)) {\n            continue;\n        }\n        // select must contain at least one thing from top\n        if top.iter().all(|c| !output.contains(c)) {\n            continue;\n        }\n\n        // determine DISTINCT\n        let mut distinct = false;\n        // INTERSECT ALL can become except INTERSECT DISTINCT\n        // - if top is DISTINCT or\n        // - if output is DISTINCT\n        if res.len() > 1 {\n            if let Distinct = &res[res.len() - 2] {\n                distinct = true;\n            }\n        }\n        if let Some(SqlTransform::Distinct) = pipeline.peek() {\n            distinct = true;\n        }\n\n        if !distinct && !ctx.dialect.intersect_all() {\n            // INTERCEPT ALL is not supported\n            // can we fall back to anti-join?\n            if ctx.anchor.contains_wildcard(&top) || ctx.anchor.contains_wildcard(&bottom) {\n                return Err(Error::new_simple(format!(\"The dialect {:?} does not support INTERSECT ALL\", ctx.dialect))\n                    .push_hint(\"providing more column information will allow the query to be translated to an anti-join.\"));\n            } else {\n                // Don't create Intercept, fallback to inner join.\n                continue;\n            }\n        }\n\n        // remove \"used up transforms\"\n        let join = res.pop(); // join\n        let (_, with, _) = join.unwrap().into_join().unwrap();\n\n        if distinct {\n            if let Some(Distinct) = &res.last() {\n                res.pop();\n            }\n            if let Some(SqlTransform::Distinct) = pipeline.peek() {\n                pipeline.next();\n            }\n        }\n\n        // push the new transform\n        res.push(SqlTransform::Intersect {\n            bottom: with,\n            distinct,\n        });\n    }\n\n    Ok(res)\n}\n\n/// Returns true if all cids are in exprs\nfn all_in(cids: &[CId], exprs: Vec<&Expr>) -> bool {\n    let exprs = col_refs(exprs);\n    cids.iter().all(|c| exprs.contains(c))\n}\n\nfn all_null(exprs: Vec<&Expr>) -> bool {\n    exprs\n        .iter()\n        .all(|e| matches!(e.kind, ExprKind::Literal(Literal::Null)))\n}\n\n/// Converts `(a == b) and ((c == d) and (e == f))`\n/// into `([a, c, e], [b, d, f])`\nfn collect_equals(expr: &Expr) -> Result<(Vec<&Expr>, Vec<&Expr>)> {\n    let mut lefts = Vec::new();\n    let mut rights = Vec::new();\n\n    match &expr.kind {\n        ExprKind::Operator { name, args } if name == \"std.eq\" && args.len() == 2 => {\n            lefts.push(&args[0]);\n            rights.push(&args[1]);\n        }\n        ExprKind::Operator { name, args } if name == \"std.and\" && args.len() == 2 => {\n            let (l, r) = collect_equals(&args[0])?;\n            lefts.extend(l);\n            rights.extend(r);\n\n            let (l, r) = collect_equals(&args[1])?;\n            lefts.extend(l);\n            rights.extend(r);\n        }\n        _ => (),\n    }\n\n    Ok((lefts, rights))\n}\n\nfn col_refs(exprs: Vec<&Expr>) -> Vec<CId> {\n    exprs\n        .into_iter()\n        .flat_map(|expr| expr.kind.as_column_ref().cloned())\n        .collect()\n}\n\n/// Pull Compute transforms in front of other transforms if possible.\n/// Position of Compute is important for two reasons:\n/// - when splitting pipelines, they provide information in which pipeline the\n///   column is computed and subsequently, with which table name should be used\n///   for name materialization.\n/// - the transform order in SQL requires Computes to be before Filter. This\n///   can be circumvented by materializing the column earlier in the pipeline,\n///   which is done in this function.\npub(in crate::sql) fn reorder(mut pipeline: Vec<SqlTransform>) -> Vec<SqlTransform> {\n    use SqlTransform::Super;\n    use Transform::*;\n\n    // iter over Computes\n    for i in 1..pipeline.len() {\n        if !matches!(&pipeline[i], Super(Compute(_))) {\n            continue;\n        }\n\n        // iter all preceding transforms\n        for j in 0..(i - 1) {\n            let compute_i = i - j;\n            let prev_i = compute_i - 1;\n\n            let compute = pipeline[compute_i]\n                .as_super()\n                .unwrap()\n                .as_compute()\n                .unwrap();\n            let prev = &pipeline[prev_i];\n\n            let should_swap = match prev {\n                // don't reorder with From or Join or another Compute\n                SqlTransform::From(_) | SqlTransform::Join { .. } | Super(Compute(_)) => false,\n\n                // reorder always\n                Super(Sort(_)) => true,\n\n                // reorder if col decl is plain\n                Super(Take(_)) if infer_complexity(compute) == Complexity::Plain => true,\n\n                // don't reorder by default\n                _ => false,\n            };\n\n            if should_swap {\n                pipeline.swap(compute_i, prev_i);\n            } else {\n                break;\n            }\n        }\n    }\n    pipeline\n}\n\n/// Normalize query:\n/// - Swap null checks such that null is always on the right side.\n///   This is needed to simplify code for Except and for compiling to IS NULL.\npub(in crate::sql) fn normalize(pipeline: Vec<Transform>) -> Result<Vec<Transform>> {\n    Normalizer {}.fold_transforms(pipeline)\n}\n\nstruct Normalizer {}\n\nimpl RqFold for Normalizer {\n    fn fold_expr(&mut self, expr: Expr) -> Result<Expr> {\n        let expr = Expr {\n            kind: rq::fold_expr_kind(self, expr.kind)?,\n            ..expr\n        };\n\n        if let ExprKind::Operator { name, args } = &expr.kind {\n            if name == \"std.eq\" && args.len() == 2 {\n                let (left, right) = (&args[0], &args[1]);\n                let span = expr.span;\n                let new_args = if let ExprKind::Literal(Literal::Null) = &left.kind {\n                    vec![right.clone(), left.clone()]\n                } else {\n                    vec![left.clone(), right.clone()]\n                };\n                let new_kind = ExprKind::Operator {\n                    name: name.clone(),\n                    args: new_args,\n                };\n                return Ok(Expr {\n                    kind: new_kind,\n                    span,\n                });\n            }\n        }\n\n        Ok(expr)\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/sql/std.sql.prql",
    "content": "#! Implementation of `std` module.\n#!\n#! This file is not really PRQL.\n#! It can contain only:\n#! - functions declarations that don't have named params and s-string-only body,\n#! - module declarations whose names correspond to sql dialect names.\n#!\n#! Functions can define `binding_strength` annotation, which signifies how much\n#! precedence does the top-level operation in the s-string provide.\n#! This value defaults to 100 (high precedence).\n#!\n#! S-strings can define required binding strength of the interpolated expression.\n#! This value defaults to binding strength of the function.\n\n\n\n# Aggregation functions\n@{window_frame=true}\nlet min = column -> s\"MIN({column:0})\"\n\n@{window_frame=true}\nlet max = column -> s\"MAX({column:0})\"\n\n@{window_frame=true, coalesce=\"0\"}\nlet sum = column -> s\"SUM({column:0})\"\n\n@{window_frame=true}\nlet average = column -> s\"AVG({column:0})\"\n\n@{window_frame=true}\nlet stddev = column -> s\"STDDEV({column:0})\"\n\n@{window_frame=true, coalesce=\"TRUE\"}\nlet all = column -> s\"BOOL_AND({column:0})\"\n\n@{window_frame=true, coalesce=\"FALSE\"}\nlet any = column -> s\"BOOL_OR({column:0})\"\n\n@{window_frame=true, coalesce=\"''\"}\nlet concat_array = column -> s\"STRING_AGG({column:0}, '')\"\n\n@{window_frame=true}\nlet count = column -> s\"COUNT(*)\"\n\n@{window_frame=true}\nlet count_distinct = column -> s\"COUNT(DISTINCT {column:0})\"\n\n# Window functions\nlet lag = offset column -> s\"LAG({column:0}, {offset:0})\"\n\nlet lead = offset column -> s\"LEAD({column:0}, {offset:0})\"\n\nlet first = column -> s\"FIRST_VALUE({column:0})\"\n\nlet last = column -> s\"LAST_VALUE({column:0})\"\n\nlet rank = -> s\"RANK()\"\n\nlet rank_dense = -> s\"DENSE_RANK()\"\n\nlet row_number = -> s\"ROW_NUMBER()\"\n\n# Mathematical functions\nmodule math {\n  # Clickhouse: https://clickhouse.com/docs/en/sql-reference/functions/math-functions\n  # DuckDB: https://duckdb.org/docs/test/functions/math.html\n  # MariaDB: https://mariadb.com/kb/en/numeric-functions/\n  # MySQL: https://dev.mysql.com/doc/refman/8.4/en/mathematical-functions.html\n  # Postgres: https://www.postgresql.org/docs/current/functions-math.html\n  # SQLite: https://www.sqlite.org/lang_mathfunc.html\n  # MSSQL: https://learn.microsoft.com/en-us/sql/t-sql/functions/mathematical-functions-transact-sql\n  # BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions\n  # Snowflake: https://docs.snowflake.com/en/sql-reference/functions-math.html\n  let abs = column -> s\"ABS({column:0})\"\n  let floor = column -> s\"FLOOR({column:0})\"\n  let ceil = column -> s\"CEIL({column:0})\"\n  let pi = -> s\"PI()\"\n  let exp = column -> s\"EXP({column:0})\"\n  let ln = column -> s\"LN({column:0})\"\n  let log10 = column -> s\"LOG10({column:0})\"\n  let log = base column -> s\"LOG10({column:0}) / LOG10({base:0})\"\n  let sqrt = column -> s\"SQRT({column:0})\"\n  let degrees = column -> s\"DEGREES({column:0})\"\n  let radians = column -> s\"RADIANS({column:0})\"\n  let cos = column -> s\"COS({column:0})\"\n  let acos = column -> s\"ACOS({column:0})\"\n  let sin = column -> s\"SIN({column:0})\"\n  let asin = column -> s\"ASIN({column:0})\"\n  let tan = column -> s\"TAN({column:0})\"\n  let atan = column -> s\"ATAN({column:0})\"\n  # Note exponent goes first, so `pow 2 3` is 2^3\n  let pow = exponent column -> s\"POW({column:0}, {exponent:0})\"\n  let round = n_digits column -> s\"ROUND({column:0}, {n_digits:0})\"\n}\n\n# Other functions\nlet as = `type` column -> s\"CAST({column:0} AS {type:0})\"\n\n# Text functions\nmodule text {\n  # Clickhouse: https://clickhouse.com/docs/en/sql-reference/functions/string-functions\n  # DuckDB: https://duckdb.org/docs/sql/functions/char\n  # MariaDB: https://mariadb.com/kb/en/string-functions/\n  # MySQL: https://dev.mysql.com/doc/refman/8.4/en/string-functions.html\n  # Postgres: https://www.postgresql.org/docs/current/functions-string.html\n  # SQLite: https://www.sqlite.org/lang_corefunc.html\n  # MSSQL: https://learn.microsoft.com/en-us/sql/t-sql/functions/string-functions-transact-sql\n  # BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions\n  # Snowflake: https://docs.snowflake.com/en/sql-reference/functions-string\n  let lower = column -> s\"LOWER({column:0})\"\n  let upper = column -> s\"UPPER({column:0})\"\n  let ltrim = column -> s\"LTRIM({column:0})\"\n  let rtrim = column -> s\"RTRIM({column:0})\"\n  let trim = column -> s\"TRIM({column:0})\"\n  let length = column -> s\"CHAR_LENGTH({column:0})\"\n  let extract = offset length column -> s\"SUBSTRING({column:0}, {offset:0}, {length:0})\"\n  let replace = pattern replacement column -> s\"REPLACE({column:0}, {pattern:0}, {replacement:0})\"\n  let starts_with = prefix column -> s\"{column:0} LIKE CONCAT({prefix:0}, '%')\"\n  let contains = substr column -> s\"{column:0} LIKE CONCAT('%', {substr:0}, '%')\"\n  let ends_with = suffix column -> s\"{column:0} LIKE CONCAT('%', {suffix:0})\"\n}\n\n# Source-reading functions, primarily for DuckDB\nlet read_parquet = binary_as_string file_row_number hive_partitioning union_by_name source -> s\"read_parquet({source:0})\"\nlet read_csv = source -> s\"read_csv({source:0})\"\nlet read_json = source -> s\"read_json({source:0})\"\n\n@{binding_strength=11}\nlet mul = l r -> null\n\n@{binding_strength=100}\nlet div_i = l r -> s\"FLOOR(ABS({l:11} / {r:12})) * SIGN({l:0}) * SIGN({r:0})\"\n\n# We have a simple float division by default, but it can be overridden by\n# dialects.\n# Note that this uses `12` for the RHS binding strength, which is one more than\n# binding strength of division. That's because we don't use associativity here,\n# and so we need to make sure that the RHS is parenthesized when the binding\n# strengths are equal; e.g. in `a / (b / c)`.\n@{binding_strength=11}\nlet div_f = l r -> s\"{l} / {r:12}\"\n\n@{binding_strength=11}\nlet mod = l r -> s\"{l} % {r:12}\"\n\n@{binding_strength=10}\nlet add = l r -> null\n\n@{binding_strength=10}\nlet sub = l r -> null\n\n@{binding_strength=6}\nlet eq = l r -> null\n\n@{binding_strength=6}\nlet ne = l r -> null\n\n@{binding_strength=6}\nlet gt = l r -> null\n\n@{binding_strength=6}\nlet lt = l r -> null\n\n@{binding_strength=6}\nlet gte = l r -> null\n\n@{binding_strength=6}\nlet lte = l r -> null\n\n@{binding_strength=3}\nlet and = l r -> null\n\n@{binding_strength=2}\nlet or = l r -> null\n\nlet coalesce = l r -> s\"COALESCE({l:0}, {r:0})\"\n\nlet regex_search = text pattern -> s\"REGEXP({text:0}, {pattern:0})\"\n\n@{binding_strength=13}\nlet neg = l -> s\"-{l}\"\n\n@{binding_strength=4}\nlet not = l -> s\"NOT {l}\"\n\nmodule ansi {\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} * 1.0 / {r:12})\"\n}\n\nmodule bigquery {\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} * 1.0 / {r:12})\"\n\n  # Mathematical functions\n  module math {\n    # BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions\n    let degrees = column -> s\"({column:0} * 180 / PI())\"\n    let radians = column -> s\"({column:0} * PI() / 180)\"\n  }\n\n  module date {\n    let to_text = format column -> s\"FORMAT_TIMESTAMP({format:0}, CAST({column:0} AS TIMESTAMP))\"\n  }\n\n  let regex_search = text pattern -> s\"REGEXP_CONTAINS({text:0}, {pattern:0})\"\n}\n\nmodule clickhouse {\n  # https://clickhouse.com/docs/en/sql-reference/functions/arithmetic-functions#divide\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} / {r:12})\"\n\n  @{binding_strength=11}\n  let div_i = l r -> s\"({l} DIV {r:12})\"\n\n  # Date functions\n  module date {\n    # https://clickhouse.com/docs/en/sql-reference/functions/date-time-functions\n    let to_text = format column -> s\"formatDateTimeInJodaSyntax({column:0}, {format:0})\"\n  }\n\n  let regex_search = text pattern -> s\"match({text:0}, {pattern:0})\"\n\n  let read_csv = source -> s\"file({source:0}, 'CSV')\"\n  let read_json = source -> s\"file({source:0}, 'Json')\"\n  let read_parquet = source -> s\"file({source:0}, 'Parquet')\"\n}\n\nmodule duckdb {\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} / {r:12})\"\n\n  @{binding_strength=11}\n  let div_i = l r -> s\"TRUNC({l:11} / {r:12})\"\n\n  # Text functions\n  module text {\n    # DuckDB: https://duckdb.org/docs/sql/functions/char\n    let length = column -> s\"LENGTH({column:0})\"\n  }\n\n  # Date functions\n  module date {\n    # https://duckdb.org/docs/sql/functions/dateformat\n    let to_text = format column -> s\"strftime({column:0}, {format:0})\"\n  }\n\n  let regex_search = text pattern -> s\"REGEXP_MATCHES({text:0}, {pattern:0})\"\n\n  let read_csv = source -> s\"read_csv_auto({source:0})\"\n  let read_json = source -> s\"read_json_auto({source:0})\"\n  let read_parquet = binary_as_string file_row_number hive_partitioning union_by_name source -> s\"read_parquet({source:0}, binary_as_string={binary_as_string:0}, file_row_number={file_row_number:0}, hive_partitioning={hive_partitioning:0}, union_by_name={union_by_name:0})\"\n}\n\nmodule mssql {\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} * 1.0 / {r:12})\"\n\n  # Mathematical functions\n  module math {\n    # https://learn.microsoft.com/en-us/sql/t-sql/functions/mathematical-functions-transact-sql\n    let ceil = column -> s\"CEILING({column:0})\"\n    let ln = column -> s\"LOG({column:0})\"\n    let pow = exponent column -> s\"POWER({column:0}, {exponent:0})\"\n  }\n\n  # Text functions\n  module text {\n    # https://learn.microsoft.com/en-us/sql/t-sql/functions/string-functions-transact-sql\n    let length = column -> s\"LEN({column:0})\"\n  }\n\n  # Date functions\n  module date {\n    # https://learn.microsoft.com/en-us/sql/t-sql/functions/format-transact-sql\n    let to_text = format column -> s\"FORMAT({column:0}, {format:0})\"\n  }\n\n  let regex_search = text pattern -> null\n}\n\nmodule mysql {\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} / {r:12})\"\n\n  @{binding_strength=11}\n  let div_i = l r -> s\"({l} DIV {r:12})\"\n\n  @{binding_strength=100}\n  let mod = l r -> s\"ROUND(MOD({l:0}, {r:0}))\"\n\n  # Date functions\n  module date {\n    # https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html\n    let to_text = format column -> s\"DATE_FORMAT({column:0}, {format:0})\"\n  }\n\n  # 'c' for case-sensitive\n  let regex_search = text pattern -> s\"REGEXP_LIKE({text:0}, {pattern:0}, 'c')\"\n}\n\nmodule postgres {\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} * 1.0 / {r:12})\"\n\n  @{binding_strength=100}\n  let div_i = l r -> s\"TRUNC({l:11} / {r:12})\"\n\n  # Mathematical functions\n  module math {\n    # Postgres: https://www.postgresql.org/docs/7.4/functions-math.html\n    @{binding_strength=100}\n    let round = n_digits column -> s\"ROUND(({column:0})::numeric, {n_digits:0})\"\n  }\n\n  # Text functions\n  module text {\n    # Postgres: https://www.postgresql.org/docs/7.4/functions-string.html\n    let extract = offset length column -> s\"SUBSTR({column:0}, {offset:0}, {length:0})\"\n  }\n\n  # Date functions\n  module date {\n    # https://www.postgresql.org/docs/current/functions-formatting.html\n    let to_text = format column -> s\"TO_CHAR({column:0}, {format:0})\"\n  }\n\n  @{binding_strength=9}\n  let regex_search = text pattern -> s\"{text} ~ {pattern}\"\n}\n\nmodule redshift {\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} * 1.0 / {r:12})\"\n\n  # Text functions\n  module text {\n    # https://docs.aws.amazon.com/redshift/latest/dg/r_concat_op.html\n    let contains = substr column -> s\"{column:0} LIKE '%' || {substr:0} || '%'\"\n  }\n}\n\nmodule glaredb {\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} * 1.0 / {r:12})\"\n\n  @{binding_strength=100}\n  let div_i = l r -> s\"TRUNC({l:11} / {r:12})\"\n\n  # Mathematical functions\n  module math {\n    # GlareDB: https://glaredb.com/docs/sql-reference/functions/math-functions\n    @{binding_strength=100}\n    let round = n_digits column -> s\"ROUND(({column:0})::numeric, {n_digits:0})\"\n  }\n\n  @{binding_strength=9}\n  let regex_search = text pattern -> s\"{text} ~ {pattern}\"\n\n  let read_csv = source -> s\"csv_scan({source:0})\"\n\n  let read_parquet = source -> s\"parquet_scan({source:0})\"\n}\n\nmodule sqlite {\n  @{window_frame=true, coalesce=\"TRUE\", binding_strength=6}\n  let all = column -> s\"MIN({column:0}) > 0\"\n\n  @{window_frame=true, coalesce=\"FALSE\", binding_strength=6}\n  let any = column -> s\"MAX({column:0}) > 0\"\n\n  @{window_frame=true, coalesce=\"''\"}\n  let concat_array = column -> s\"GROUP_CONCAT({column:0}, '')\"\n\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} * 1.0 / {r:12})\"\n\n  @{binding_strength=100}\n  let div_i = l r -> s\"ROUND(ABS({l:11} / {r:12}) - 0.5) * SIGN({l:0}) * SIGN({r:0})\"\n\n  # Text functions\n  module text {\n    # SQLite: https://www.sqlite.org/lang_corefunc.html\n    let length = column -> s\"LENGTH({column:0})\"\n    let starts_with = prefix column -> s\"{column:0} LIKE {prefix:0} || '%'\"\n    let contains = substr column -> s\"{column:0} LIKE '%' || {substr:0} || '%'\"\n    let ends_with = suffix column -> s\"{column:0} LIKE '%' || {suffix:0}\"\n  }\n\n  @{binding_strength=9}\n  let regex_search = text pattern -> s\"{text} REGEXP {pattern}\"\n}\n\nmodule snowflake {\n  # https://docs.snowflake.com/en/sql-reference/operators-arithmetic#division\n  @{binding_strength=11}\n  let div_f = l r -> s\"({l} / {r:12})\"\n\n  # Text functions\n  module text {\n    # https://docs.snowflake.com/en/sql-reference/functions-string\n    let length = column -> s\"LENGTH({column:0})\"\n  }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/utils/id_gen.rs",
    "content": "use std::marker::PhantomData;\n\nuse crate::ir::rq::{fold_table, CId, RelationalQuery, RqFold, TId, TableDecl};\nuse crate::Result;\n\n#[derive(Debug, Clone)]\npub struct IdGenerator<T: From<usize>> {\n    next_id: usize,\n    phantom: PhantomData<T>,\n}\n\nimpl<T: From<usize>> IdGenerator<T> {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    fn skip(&mut self, id: usize) {\n        self.next_id = self.next_id.max(id + 1);\n    }\n\n    pub fn gen(&mut self) -> T {\n        let id = self.next_id;\n        self.next_id += 1;\n        T::from(id)\n    }\n}\n\nimpl<T: From<usize>> Default for IdGenerator<T> {\n    fn default() -> IdGenerator<T> {\n        IdGenerator {\n            next_id: 0,\n            phantom: PhantomData,\n        }\n    }\n}\n\nimpl IdGenerator<usize> {\n    /// Returns a new id generators capable of generating new ids for given query.\n    pub fn load(query: RelationalQuery) -> (IdGenerator<CId>, IdGenerator<TId>, RelationalQuery) {\n        let mut loader = IdLoader {\n            cid: IdGenerator::<CId>::default(),\n            tid: IdGenerator::<TId>::default(),\n        };\n        let query = loader.fold_query(query).unwrap();\n        (loader.cid, loader.tid, query)\n    }\n}\nstruct IdLoader {\n    cid: IdGenerator<CId>,\n    tid: IdGenerator<TId>,\n}\n\nimpl RqFold for IdLoader {\n    fn fold_cid(&mut self, cid: CId) -> Result<CId> {\n        self.cid.skip(cid.get());\n\n        Ok(cid)\n    }\n\n    fn fold_table(&mut self, table: TableDecl) -> Result<TableDecl> {\n        self.tid.skip(table.id.get());\n\n        fold_table(self, table)\n    }\n}\n\n#[derive(Debug, Clone, Default)]\npub struct NameGenerator {\n    prefix: &'static str,\n    id: IdGenerator<usize>,\n}\n\nimpl NameGenerator {\n    pub fn new(prefix: &'static str) -> Self {\n        NameGenerator {\n            prefix,\n            id: IdGenerator::new(),\n        }\n    }\n\n    pub fn gen(&mut self) -> String {\n        format!(\"{}{}\", self.prefix, self.id.gen())\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/utils/mod.rs",
    "content": "mod id_gen;\nmod toposort;\n\nuse std::{io::stderr, sync::OnceLock};\n\nuse anstream::adapter::strip_str;\npub use id_gen::{IdGenerator, NameGenerator};\nuse itertools::Itertools;\nuse regex::Regex;\npub use toposort::toposort;\n\nuse crate::Result;\n\npub trait OrMap<T> {\n    /// Merges two options into one using `f`.\n    /// If one of the options is None, results defaults to the other one.\n    fn or_map<F>(self, b: Self, f: F) -> Self\n    where\n        F: FnOnce(T, T) -> T;\n}\n\nimpl<T> OrMap<T> for Option<T> {\n    fn or_map<F>(self, b: Self, f: F) -> Self\n    where\n        F: FnOnce(T, T) -> T,\n    {\n        match (self, b) {\n            (Some(a), Some(b)) => Some(f(a, b)),\n            (a, None) => a,\n            (None, b) => b,\n        }\n    }\n}\n\npub trait Pluck<T> {\n    fn pluck<R, F>(&mut self, f: F) -> Vec<R>\n    where\n        F: Fn(T) -> Result<R, T>;\n}\n\nimpl<T> Pluck<T> for Vec<T> {\n    fn pluck<R, F>(&mut self, f: F) -> Vec<R>\n    where\n        F: Fn(T) -> Result<R, T>,\n    {\n        let mut matched = Vec::new();\n        let mut not_matched = Vec::new();\n\n        for transform in self.drain(..) {\n            match f(transform) {\n                Ok(t) => matched.push(t),\n                Err(transform) => not_matched.push(transform),\n            }\n        }\n\n        self.extend(not_matched);\n        matched\n    }\n}\n\n/// Breaks up a [Vec] into two parts at the position of first matching element.\n/// Matching element is placed into the second part.\n///\n/// Zero clones.\npub trait BreakUp<T> {\n    fn break_up<F>(self, f: F) -> (Vec<T>, Vec<T>)\n    where\n        F: FnMut(&T) -> bool;\n}\n\nimpl<T> BreakUp<T> for Vec<T> {\n    fn break_up<F>(mut self, f: F) -> (Vec<T>, Vec<T>)\n    where\n        F: FnMut(&T) -> bool,\n    {\n        let position = self.iter().position(f).unwrap_or(self.len());\n        let after = self.drain(position..).collect_vec();\n        (self, after)\n    }\n}\n\npub(crate) fn valid_ident() -> &'static Regex {\n    static VALID_IDENT: OnceLock<Regex> = OnceLock::new();\n    VALID_IDENT.get_or_init(|| {\n        // One of:\n        // - `*`\n        // - An ident starting with `a-z_\\$` and containing other characters `a-z0-9_\\$`\n        //\n        // We could replace this with pomsky (regex<>pomsky : sql<>prql)\n        // ^ ('*' | [ascii_lower '_$'] [ascii_lower ascii_digit '_$']* ) $\n        Regex::new(r\"^((\\*)|(^[a-z_\\$][a-z0-9_\\$]*))$\").unwrap()\n    })\n}\n\nfn should_use_color() -> bool {\n    match anstream::AutoStream::choice(&stderr()) {\n        anstream::ColorChoice::Auto => true,\n        anstream::ColorChoice::Always => true,\n        anstream::ColorChoice::AlwaysAnsi => true,\n        anstream::ColorChoice::Never => false,\n    }\n}\n\n/// Strip colors, for external libraries which don't yet strip themselves, and\n/// for insta snapshot tests. This will respond to environment variables such as\n/// `CLI_COLOR`.\npub fn maybe_strip_colors(s: &str) -> String {\n    if !should_use_color() {\n        strip_str(s).to_string()\n    } else {\n        s.to_string()\n    }\n}\n\n#[test]\nfn test_write_ident_part() {\n    assert!(!valid_ident().is_match(\"\"));\n}\n"
  },
  {
    "path": "prqlc/prqlc/src/utils/toposort.rs",
    "content": "use std::collections::HashMap;\n\ntype Dag = Vec<Vec<usize>>;\n\nstruct Toposort {\n    nodes: Vec<Node>,\n    order: Vec<usize>,\n}\n\n#[derive(Clone, Copy)]\nstruct Node {\n    visiting: bool,\n    done: bool,\n}\n\npub fn toposort<'a, Key: Eq + std::hash::Hash + Clone>(\n    dependencies: &'a [(Key, Vec<Key>)],\n    start: Option<&'_ Key>,\n) -> Option<Vec<&'a Key>> {\n    // create mapping from Key to usize\n    let index: HashMap<&Key, usize> = dependencies\n        .iter()\n        .enumerate()\n        .map(|(index, (key, _))| (key, index))\n        .collect();\n\n    // map DAG from Key to usize\n    let dag: Dag = dependencies\n        .iter()\n        .map(|(_, deps)| deps.iter().flat_map(|d| index.get(d).cloned()).collect())\n        .collect();\n\n    // init toposort\n    let empty = Node {\n        visiting: false,\n        done: false,\n    };\n    let mut toposort = Toposort {\n        nodes: vec![empty; index.len()],\n        order: Vec::with_capacity(index.len()),\n    };\n\n    if let Some(start) = start.map(|s| index.get(s).unwrap()) {\n        // use only the provided visit start\n        toposort.visit(&dag, *start).ok()?;\n    } else {\n        // start visits from all nodes\n        while toposort.order.len() < dependencies.len() {\n            for start_at in 0..index.len() {\n                toposort.visit(&dag, start_at).ok()?;\n            }\n        }\n    }\n\n    // unmap\n    Some(toposort.order.iter().map(|i| &dependencies[*i].0).collect())\n}\n\nimpl Toposort {\n    fn visit(&mut self, dag: &Dag, n: usize) -> Result<(), ()> {\n        let node = self.nodes.get_mut(n).unwrap();\n        if node.done {\n            return Ok(());\n        }\n        if node.visiting {\n            return Err(());\n        }\n        node.visiting = true;\n\n        for m in &dag[n] {\n            self.visit(dag, *m)?;\n        }\n\n        let node = self.nodes.get_mut(n).unwrap();\n        node.visiting = false;\n        node.done = true;\n        self.order.push(n);\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use itertools::Itertools;\n\n    use super::toposort;\n\n    #[test]\n    fn normal_sort() {\n        let dependencies = vec![\n            (\"a\", vec![\"b\"]),\n            (\"b\", vec![\"c\"]),\n            (\"c\", vec![]),\n            (\"d\", vec![]),\n        ];\n        let order = toposort(&dependencies, None).unwrap();\n\n        let order = order.into_iter().copied().collect_vec();\n        assert_eq!(order, vec![\"c\", \"b\", \"a\", \"d\"]);\n    }\n\n    #[test]\n    fn normal_sort_2() {\n        let dependencies = vec![\n            (\"a\", vec![]),\n            (\"b\", vec![]),\n            (\"c\", vec![\"b\"]),\n            (\"d\", vec![\"c\"]),\n        ];\n        let order = toposort(&dependencies, None).unwrap();\n\n        let order = order.into_iter().copied().collect_vec();\n        assert_eq!(order, vec![\"a\", \"b\", \"c\", \"d\"]);\n    }\n\n    #[test]\n    fn dag_with_cycle() {\n        let dependencies = vec![\n            (\"a\", vec![\"b\"]),\n            (\"b\", vec![\"c\", \"d\"]),\n            (\"c\", vec![]),\n            (\"d\", vec![\"a\"]),\n        ];\n        let order = toposort(&dependencies, None);\n\n        assert!(order.is_none());\n    }\n\n    #[test]\n    fn parallel_when_ambiguous() {\n        let dependencies = vec![\n            (\"a\", vec![\"b\"]),\n            (\"b\", vec![]),\n            (\"c\", vec![\"b\"]),\n            (\"d\", vec![\"b\"]),\n        ];\n\n        let order = toposort(&dependencies, None).unwrap();\n\n        let order = order.into_iter().copied().collect_vec();\n        assert_eq!(order, vec![\"b\", \"a\", \"c\", \"d\"]);\n    }\n\n    #[test]\n    fn with_root() {\n        let dependencies = vec![\n            (\"a\", vec![\"b\"]),\n            (\"b\", vec![]),\n            (\"c\", vec![\"b\"]),\n            (\"d\", vec![\"b\"]),\n        ];\n        let root = \"c\";\n\n        let order = toposort(&dependencies, Some(&root)).unwrap();\n\n        let order = order.into_iter().copied().collect_vec();\n        assert_eq!(order, vec![\"b\", \"c\"]);\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/CLAUDE.md",
    "content": "# Tests\n\n## Structure\n\n- **`integration/sql.rs`** — Unit tests for PRQL→SQL compilation. Fast, focused,\n  preferred for most changes.\n- **`integration/queries/*.prql`** — Integration tests generating 6 snapshots\n  each (different SQL dialects). Use sparingly.\n- **`integration/error_messages.rs`** — Tests for compiler error diagnostics.\n- **`integration/dbs/`** — Database runners for executing queries against real\n  databases.\n\n## Guidelines\n\n**80% unit tests, 20% integration tests.** Integration tests in\n`integration/queries/` are verbose — each `.prql` file generates six snapshot\nfiles. Prefer unit tests in [`integration/sql.rs`](./integration/sql.rs):\n\n```rust\n#[test]\nfn test_my_feature() {\n    assert_snapshot!(compile(r#\"\n    from foo\n    select bar\n    \"#).unwrap(), @\"...\");\n}\n```\n\nNew integration test files are appropriate when validating against real\ndatabases or testing behavior across compilation stages. Extend existing tests\nwhen possible.\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/bad_error_messages.rs",
    "content": "//! Record bad error messages here which we should improve.\n//!\n//! Some of these will be good issues for new contributors, or for more\n//! experienced contributors who would like a quick issue to fix:\n//! - Find where the error is being raised now, generally just search for a part\n//!   of the message.\n//! - Add `` macros to the code to see what's going on.\n//! - Write a better message / find a better place to raise a message.\n//! - Run `cargo insta test --accept`, and move the test out of this file into\n//!   `test_error_messages.rs`. If it's only partially solved, add a TODO and\n//!   make a call for where it should go.\n//!\n//! Adding bad error messages here is also a welcome contribution. Probably\n//! one-issue-per-error-message is not a good way of managing them — there would\n//! be a huge number of issues, and it would be difficult to see what's current.\n//! So instead, add the error message as a test here.\n\nuse insta::assert_snapshot;\n\nuse super::sql::compile;\n\n#[test]\nfn test_bad_error_messages() {\n    assert_snapshot!(compile(r###\"\n    from film\n    group\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :3:5 ]\n       │\n     3 │     group\n       │     ──┬──\n       │       ╰──── main expected type `relation`, but found type `func transform relation -> relation`\n       │\n       │ Help: Argument might be missing to function std.group?\n       │\n       │ Note: Type `relation` expands to `[{..}]`\n    ───╯\n    \");\n\n    // This should suggest parentheses (this might not be an easy one to solve)\n    assert_snapshot!(compile(r#\"\n    let f = country -> country == \"Canada\"\n\n    from employees\n    filter f location\n    \"#).unwrap_err(), @r\"\n    Error:\n       ╭─[ :5:14 ]\n       │\n     5 │     filter f location\n       │              ────┬───\n       │                  ╰───── Unknown name `location`\n    ───╯\n    \");\n\n    // Really complicated error message for something so fundamental\n    assert_snapshot!(compile(r###\"\n    select tracks\n    from artists\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :3:5 ]\n       │\n     3 │     from artists\n       │     ──────┬─────\n       │           ╰─────── expected a function, but found `default_db.artists`\n    ───╯\n    \");\n}\n\n#[test]\nfn interpolation_end() {\n    use insta::assert_debug_snapshot;\n\n    // This test demonstrates error reporting for an unclosed f-string: `f\"{}` (no closing quote).\n    // The input ends at position 20 after the `}`, so the closing quote is missing at position 20.\n    //\n    // The lexer correctly reports the error at position 20 (end of input) with `found: \"\"`.\n    // The parser reports the error at position 21 (character position in line 1) with \"unexpected\".\n\n    let source = r#\"from x | select f\"{}\"#;\n\n    // LEXER output (for comparison with parser output below):\n    assert_debug_snapshot!(prqlc_parser::lexer::lex_source(source).unwrap_err(), @r#\"\n    [\n        Error {\n            kind: Error,\n            span: Some(\n                0:20-20,\n            ),\n            reason: Unexpected {\n                found: \"end of input\",\n            },\n            hints: [],\n            code: None,\n        },\n    ]\n    \"#);\n\n    // PARSER output (full compilation error):\n    assert_snapshot!(compile(source).unwrap_err(), @r#\"\n    Error:\n       ╭─[ :1:21 ]\n       │\n     1 │ from x | select f\"{}\n       │                     │\n       │                     ╰─ unexpected end of input\n    ───╯\n    \"#);\n}\n\n#[test]\nfn select_with_extra_fstr() {\n    // Should complain in the same way as `select lower \"mooo\"`\n    assert_snapshot!(compile(r#\"\n    from foo\n    select lower f\"{x}/{y}\"\n    \"#).unwrap_err(), @r#\"\n    Error:\n       ╭─[ :3:21 ]\n       │\n     3 │     select lower f\"{x}/{y}\"\n       │                     ┬\n       │                     ╰── Unknown name `x`\n    ───╯\n    \"#);\n}\n\n// See also test_error_messages::test_type_error_placement\n#[test]\nfn misplaced_type_error() {\n    // This one should point at `foo` in `select (... foo)`\n    // (preferably in addition to the error that is currently generated)\n    assert_snapshot!(compile(r###\"\n    let foo = 123\n    from t\n    select (true && foo)\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :2:15 ]\n       │\n     2 │     let foo = 123\n       │               ─┬─\n       │                ╰─── function std.and, param `right` expected type `bool`, but found type `int`\n    ───╯\n    \");\n}\n\n#[test]\nfn test_hint_missing_args() {\n    assert_snapshot!(compile(r###\"\n    from film\n    select {film_id, lag film_id}\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :3:22 ]\n       │\n     3 │     select {film_id, lag film_id}\n       │                      ─────┬─────\n       │                           ╰─────── unexpected `(func offset <int> column <array> -> internal std.lag) film_id`\n       │\n       │ Help: this is probably a 'bad type' error (we are working on that)\n    ───╯\n    \")\n}\n\n#[test]\nfn test_relation_literal_contains_literals() {\n    assert_snapshot!(compile(r###\"\n    [{a=(1+1)}]\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :2:9 ]\n       │\n     2 │     [{a=(1+1)}]\n       │         ──┬──\n       │           ╰──── relation literal expected literals, but found ``(std.add ...)``\n    ───╯\n    \")\n}\n\n#[test]\nfn nested_groups() {\n    // Nested `group` gives a very abstract & internally-focused error message\n    assert_snapshot!(compile(r###\"\n    from invoices\n    select {inv = this}\n    join item = invoice_items (==invoice_id)\n\n    group { inv.billing_city } (\n\n      group { item.name } (\n        aggregate {\n          ct1 = count inv.name,\n        }\n      )\n    )\n    \"###).unwrap_err(), @r\"\n    Error:\n        ╭─[ :9:9 ]\n        │\n      9 │ ╭─▶         aggregate {\n        ┆ ┆\n     11 │ ├─▶         }\n        │ │\n        │ ╰─────────────── internal compiler error; tracked at https://github.com/PRQL/prql/issues/3870\n    ────╯\n    \");\n}\n\n#[test]\nfn just_std() {\n    assert_snapshot!(compile(r###\"\n    std\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :1:1 ]\n       │\n     1 │ ╭─▶\n     2 │ ├─▶     std\n       │ │\n       │ ╰───────────── internal compiler error; tracked at https://github.com/PRQL/prql/issues/4474\n    ───╯\n    \");\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/albums.csv",
    "content": "album_id,title,artist_id\n1,For Those About To Rock We Salute You,1\n2,Balls to the Wall,2\n3,Restless and Wild,2\n4,Let There Be Rock,1\n5,Big Ones,3\n6,Jagged Little Pill,4\n7,Facelift,5\n8,Warner 25 Anos,6\n9,Plays Metallica By Four Cellos,7\n10,Audioslave,8\n11,Out Of Exile,8\n12,BackBeat Soundtrack,9\n13,The Best Of Billy Cobham,10\n14,Alcohol Fueled Brewtality Live! [Disc 1],11\n15,Alcohol Fueled Brewtality Live! [Disc 2],11\n16,Black Sabbath,12\n17,Black Sabbath Vol. 4 (Remaster),12\n18,Body Count,13\n19,Chemical Wedding,14\n20,The Best Of Buddy Guy - The Millenium Collection,15\n21,Prenda Minha,16\n22,Sozinho Remix Ao Vivo,16\n23,Minha Historia,17\n24,Afrociberdelia,18\n25,Da Lama Ao Caos,18\n26,Acústico MTV [Live],19\n27,Cidade Negra - Hits,19\n28,Na Pista,20\n29,Axé Bahia 2001,21\n30,BBC Sessions [Disc 1] [Live],22\n31,Bongo Fury,23\n32,Carnaval 2001,21\n33,Chill: Brazil (Disc 1),24\n34,Chill: Brazil (Disc 2),6\n35,Garage Inc. (Disc 1),50\n36,Greatest Hits II,51\n37,Greatest Kiss,52\n38,Heart of the Night,53\n39,International Superhits,54\n40,Into The Light,55\n41,Meus Momentos,56\n42,Minha História,57\n43,MK III The Final Concerts [Disc 1],58\n44,Physical Graffiti [Disc 1],22\n45,Sambas De Enredo 2001,21\n46,Supernatural,59\n47,The Best of Ed Motta,37\n48,The Essential Miles Davis [Disc 1],68\n49,The Essential Miles Davis [Disc 2],68\n50,The Final Concerts (Disc 2),58\n51,Up An' Atom,69\n52,Vinícius De Moraes - Sem Limite,70\n53,Vozes do MPB,21\n54,\"Chronicle, Vol. 1\",76\n55,\"Chronicle, Vol. 2\",76\n56,Cássia Eller - Coleção Sem Limite [Disc 2],77\n57,Cássia Eller - Sem Limite [Disc 1],77\n58,Come Taste The Band,58\n59,Deep Purple In Rock,58\n60,Fireball,58\n61,Knocking at Your Back Door: The Best Of Deep Purple in the 80's,58\n62,Machine Head,58\n63,Purpendicular,58\n64,Slaves And Masters,58\n65,Stormbringer,58\n66,The Battle Rages On,58\n67,Vault: Def Leppard's Greatest Hits,78\n68,Outbreak,79\n69,Djavan Ao Vivo - Vol. 02,80\n70,Djavan Ao Vivo - Vol. 1,80\n71,Elis Regina-Minha História,41\n72,The Cream Of Clapton,81\n73,Unplugged,81\n74,Album Of The Year,82\n75,Angel Dust,82\n76,King For A Day Fool For A Lifetime,82\n77,The Real Thing,82\n78,Deixa Entrar,83\n79,In Your Honor [Disc 1],84\n80,In Your Honor [Disc 2],84\n81,One By One,84\n82,The Colour And The Shape,84\n83,My Way: The Best Of Frank Sinatra [Disc 1],85\n84,Roda De Funk,86\n85,As Canções de Eu Tu Eles,27\n86,Quanta Gente Veio Ver (Live),27\n87,Quanta Gente Veio ver--Bônus De Carnaval,27\n88,Faceless,87\n89,American Idiot,54\n90,Appetite for Destruction,88\n91,Use Your Illusion I,88\n92,Use Your Illusion II,88\n93,Blue Moods,89\n94,A Matter of Life and Death,90\n95,A Real Dead One,90\n96,A Real Live One,90\n97,Brave New World,90\n98,Dance Of Death,90\n99,Fear Of The Dark,90\n100,Iron Maiden,90\n101,Killers,90\n102,Live After Death,90\n103,Live At Donington 1992 (Disc 1),90\n104,Live At Donington 1992 (Disc 2),90\n105,No Prayer For The Dying,90\n106,Piece Of Mind,90\n107,Powerslave,90\n108,Rock In Rio [CD1],90\n109,Rock In Rio [CD2],90\n110,Seventh Son of a Seventh Son,90\n111,Somewhere in Time,90\n112,The Number of The Beast,90\n113,The X Factor,90\n114,Virtual XI,90\n115,Sex Machine,91\n116,Emergency On Planet Earth,92\n117,Synkronized,92\n118,The Return Of The Space Cowboy,92\n119,Get Born,93\n120,Are You Experienced?,94\n121,Surfing with the Alien (Remastered),95\n122,Jorge Ben Jor 25 Anos,46\n123,Jota Quest-1995,96\n124,Cafezinho,97\n125,Living After Midnight,98\n126,Unplugged [Live],52\n127,BBC Sessions [Disc 2] [Live],22\n128,Coda,22\n129,Houses Of The Holy,22\n130,In Through The Out Door,22\n131,IV,22\n132,Led Zeppelin I,22\n133,Led Zeppelin II,22\n134,Led Zeppelin III,22\n135,Physical Graffiti [Disc 2],22\n136,Presence,22\n137,The Song Remains The Same (Disc 1),22\n138,The Song Remains The Same (Disc 2),22\n139,A TempestadeTempestade Ou O Livro Dos Dias,99\n140,Mais Do Mesmo,99\n141,Greatest Hits,100\n142,Lulu Santos - RCA 100 Anos De Música - Álbum 01,101\n143,Lulu Santos - RCA 100 Anos De Música - Álbum 02,101\n144,Misplaced Childhood,102\n145,Barulhinho Bom,103\n146,Seek And Shall Find: More Of The Best (1963-1981),104\n147,The Best Of Men At Work,105\n148,Black Album,50\n149,Garage Inc. (Disc 2),50\n150,Kill 'Em All,50\n151,Load,50\n152,Master Of Puppets,50\n153,ReLoad,50\n154,Ride The Lightning,50\n155,St. Anger,50\n156,...And Justice For All,50\n157,Miles Ahead,68\n158,Milton Nascimento Ao Vivo,42\n159,Minas,42\n160,Ace Of Spades,106\n161,Demorou...,108\n162,Motley Crue Greatest Hits,109\n163,From The Muddy Banks Of The Wishkah [Live],110\n164,Nevermind,110\n165,Compositores,111\n166,Olodum,112\n167,Acústico MTV,113\n168,Arquivo II,113\n169,Arquivo Os Paralamas Do Sucesso,113\n170,Bark at the Moon (Remastered),114\n171,Blizzard of Ozz,114\n172,Diary of a Madman (Remastered),114\n173,No More Tears (Remastered),114\n174,Tribute,114\n175,Walking Into Clarksdale,115\n176,Original Soundtracks 1,116\n177,The Beast Live,117\n178,Live On Two Legs [Live],118\n179,Pearl Jam,118\n180,Riot Act,118\n181,Ten,118\n182,Vs.,118\n183,Dark Side Of The Moon,120\n184,Os Cães Ladram Mas A Caravana Não Pára,121\n185,Greatest Hits I,51\n186,News Of The World,51\n187,Out Of Time,122\n188,Green,124\n189,New Adventures In Hi-Fi,124\n190,The Best Of R.E.M.: The IRS Years,124\n191,Cesta Básica,125\n192,Raul Seixas,126\n193,Blood Sugar Sex Magik,127\n194,By The Way,127\n195,Californication,127\n196,Retrospective I (1974-1980),128\n197,Santana - As Years Go By,59\n198,Santana Live,59\n199,Maquinarama,130\n200,O Samba Poconé,130\n201,Judas 0: B-Sides and Rarities,131\n202,Rotten Apples: Greatest Hits,131\n203,A-Sides,132\n204,Morning Dance,53\n205,In Step,133\n206,Core,134\n207,Mezmerize,135\n208,[1997] Black Light Syndrome,136\n209,Live [Disc 1],137\n210,Live [Disc 2],137\n211,The Singles,138\n212,Beyond Good And Evil,139\n213,\"Pure Cult: The Best Of The Cult (For Rockers, Ravers, Lovers & Sinners) [UK]\",139\n214,The Doors,140\n215,The Police Greatest Hits,141\n216,\"Hot Rocks, 1964-1971 (Disc 1)\",142\n217,No Security,142\n218,Voodoo Lounge,142\n219,Tangents,143\n220,Transmission,143\n221,My Generation - The Very Best Of The Who,144\n222,Serie Sem Limite (Disc 1),145\n223,Serie Sem Limite (Disc 2),145\n224,Acústico,146\n225,Volume Dois,146\n226,Battlestar Galactica: The Story So Far,147\n227,\"Battlestar Galactica, Season 3\",147\n228,\"Heroes, Season 1\",148\n229,\"Lost, Season 3\",149\n230,\"Lost, Season 1\",149\n231,\"Lost, Season 2\",149\n232,Achtung Baby,150\n233,All That You Can't Leave Behind,150\n234,B-Sides 1980-1990,150\n235,How To Dismantle An Atomic Bomb,150\n236,Pop,150\n237,Rattle And Hum,150\n238,The Best Of 1980-1990,150\n239,War,150\n240,Zooropa,150\n241,UB40 The Best Of - Volume Two [UK],151\n242,Diver Down,152\n243,\"The Best Of Van Halen, Vol. I\",152\n244,Van Halen,152\n245,Van Halen III,152\n246,Contraband,153\n247,Vinicius De Moraes,72\n248,Ao Vivo [IMPORT],155\n249,\"The Office, Season 1\",156\n250,\"The Office, Season 2\",156\n251,\"The Office, Season 3\",156\n252,Un-Led-Ed,157\n253,\"Battlestar Galactica (Classic), Season 1\",158\n254,Aquaman,159\n255,Instant Karma: The Amnesty International Campaign to Save Darfur,150\n256,Speak of the Devil,114\n257,20th Century Masters - The Millennium Collection: The Best of Scorpions,179\n258,House of Pain,180\n259,Radio Brasil (O Som da Jovem Vanguarda) - Seleccao de Henrique Amaro,36\n260,Cake: B-Sides and Rarities,196\n261,\"LOST, Season 4\",149\n262,Quiet Songs,197\n263,Muso Ko,198\n264,Realize,199\n265,Every Kind of Light,200\n266,Duos II,201\n267,Worlds,202\n268,The Best of Beethoven,203\n269,Temple of the Dog,204\n270,Carry On,205\n271,Revelations,8\n272,Adorate Deum: Gregorian Chant from the Proper of the Mass,206\n273,Allegri: Miserere,207\n274,Pachelbel: Canon & Gigue,208\n275,Vivaldi: The Four Seasons,209\n276,Bach: Violin Concertos,210\n277,Bach: Goldberg Variations,211\n278,Bach: The Cello Suites,212\n279,Handel: The Messiah (Highlights),213\n280,The World of Classical Favourites,214\n281,Sir Neville Marriner: A Celebration,215\n282,Mozart: Wind Concertos,216\n283,Haydn: Symphonies 99 - 104,217\n284,Beethoven: Symhonies Nos. 5 & 6,218\n285,A Soprano Inspired,219\n286,Great Opera Choruses,220\n287,Wagner: Favourite Overtures,221\n288,\"Fauré: Requiem, Ravel: Pavane & Others\",222\n289,Tchaikovsky: The Nutcracker,223\n290,The Last Night of the Proms,224\n291,Puccini: Madama Butterfly - Highlights,225\n292,\"Holst: The Planets, Op. 32 & Vaughan Williams: Fantasies\",226\n293,Pavarotti's Opera Made Easy,227\n294,Great Performances - Barber's Adagio and Other Romantic Favorites for Strings,228\n295,Carmina Burana,229\n296,\"A Copland Celebration, Vol. I\",230\n297,Bach: Toccata & Fugue in D Minor,231\n298,Prokofiev: Symphony No.1,232\n299,Scheherazade,233\n300,Bach: The Brandenburg Concertos,234\n301,Chopin: Piano Concertos Nos. 1 & 2,235\n302,Mascagni: Cavalleria Rusticana,236\n303,Sibelius: Finlandia,237\n304,Beethoven Piano Sonatas: Moonlight & Pastorale,238\n305,Great Recordings of the Century - Mahler: Das Lied von der Erde,240\n306,Elgar: Cello Concerto & Vaughan Williams: Fantasias,241\n307,\"Adams, John: The Chairman Dances\",242\n308,\"Tchaikovsky: 1812 Festival Overture, Op.49, Capriccio Italien & Beethoven: Wellington's Victory\",243\n309,Palestrina: Missa Papae Marcelli & Allegri: Miserere,244\n310,Prokofiev: Romeo & Juliet,245\n311,Strauss: Waltzes,226\n312,Berlioz: Symphonie Fantastique,245\n313,Bizet: Carmen Highlights,246\n314,English Renaissance,247\n315,Handel: Music for the Royal Fireworks (Original Version 1749),208\n316,Grieg: Peer Gynt Suites & Sibelius: Pelléas et Mélisande,248\n317,Mozart Gala: Famous Arias,249\n318,SCRIABIN: Vers la flamme,250\n319,Armada: Music from the Courts of England and Spain,251\n320,Mozart: Symphonies Nos. 40 & 41,248\n321,Back to Black,252\n322,Frank,252\n323,Carried to Dust (Bonus Track Version),253\n324,Beethoven: Symphony No. 6 'Pastoral' Etc.,254\n325,Bartok: Violin & Viola Concertos,255\n326,Mendelssohn: A Midsummer Night's Dream,256\n327,Bach: Orchestral Suites Nos. 1 - 4,257\n328,\"Charpentier: Divertissements, Airs & Concerts\",258\n329,South American Getaway,259\n330,Górecki: Symphony No. 3,260\n331,Purcell: The Fairy Queen,261\n332,The Ultimate Relexation Album,262\n333,Purcell: Music for the Queen Mary,263\n334,Weill: The Seven Deadly Sins,264\n335,\"J.S. Bach: Chaconne, Suite in E Minor, Partita in E Major & Prelude, Fugue and Allegro\",265\n336,Prokofiev: Symphony No.5 & Stravinksy: Le Sacre Du Printemps,248\n337,\"Szymanowski: Piano Works, Vol. 1\",266\n338,Nielsen: The Six Symphonies,267\n339,Great Recordings of the Century: Paganini's 24 Caprices,268\n340,Liszt - 12 Études D'Execution Transcendante,269\n341,\"Great Recordings of the Century - Shubert: Schwanengesang, 4 Lieder\",270\n342,\"Locatelli: Concertos for Violin, Strings and Continuo, Vol. 3\",271\n343,Respighi:Pines of Rome,226\n344,Schubert: The Late String Quartets & String Quintet (3 CD's),272\n345,Monteverdi: L'Orfeo,273\n346,Mozart: Chamber Music,274\n347,Koyaanisqatsi (Soundtrack from the Motion Picture),275\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/artists.csv",
    "content": "artist_id,name\n1,AC/DC\n2,Accept\n3,Aerosmith\n4,Alanis Morissette\n5,Alice In Chains\n6,Antônio Carlos Jobim\n7,Apocalyptica\n8,Audioslave\n9,BackBeat\n10,Billy Cobham\n11,Black Label Society\n12,Black Sabbath\n13,Body Count\n14,Bruce Dickinson\n15,Buddy Guy\n16,Caetano Veloso\n17,Chico Buarque\n18,Chico Science & Nação Zumbi\n19,Cidade Negra\n20,Cláudio Zoli\n21,Various Artists\n22,Led Zeppelin\n23,Frank Zappa & Captain Beefheart\n24,Marcos Valle\n25,Milton Nascimento & Bebeto\n26,Azymuth\n27,Gilberto Gil\n28,João Gilberto\n29,Bebel Gilberto\n30,Jorge Vercilo\n31,Baby Consuelo\n32,Ney Matogrosso\n33,Luiz Melodia\n34,Nando Reis\n35,Pedro Luís & A Parede\n36,O Rappa\n37,Ed Motta\n38,Banda Black Rio\n39,Fernanda Porto\n40,Os Cariocas\n41,Elis Regina\n42,Milton Nascimento\n43,A Cor Do Som\n44,Kid Abelha\n45,Sandra De Sá\n46,Jorge Ben\n47,Hermeto Pascoal\n48,Barão Vermelho\n49,\"Edson, DJ Marky & DJ Patife Featuring Fernanda Porto\"\n50,Metallica\n51,Queen\n52,Kiss\n53,Spyro Gyra\n54,Green Day\n55,David Coverdale\n56,Gonzaguinha\n57,Os Mutantes\n58,Deep Purple\n59,Santana\n60,Santana Feat. Dave Matthews\n61,Santana Feat. Everlast\n62,Santana Feat. Rob Thomas\n63,Santana Feat. Lauryn Hill & Cee-Lo\n64,Santana Feat. The Project G&B\n65,Santana Feat. Maná\n66,Santana Feat. Eagle-Eye Cherry\n67,Santana Feat. Eric Clapton\n68,Miles Davis\n69,Gene Krupa\n70,Toquinho & Vinícius\n71,Vinícius De Moraes & Baden Powell\n72,Vinícius De Moraes\n73,Vinícius E Qurteto Em Cy\n74,Vinícius E Odette Lara\n75,\"Vinicius, Toquinho & Quarteto Em Cy\"\n76,Creedence Clearwater Revival\n77,Cássia Eller\n78,Def Leppard\n79,Dennis Chambers\n80,Djavan\n81,Eric Clapton\n82,Faith No More\n83,Falamansa\n84,Foo Fighters\n85,Frank Sinatra\n86,Funk Como Le Gusta\n87,Godsmack\n88,Guns N' Roses\n89,Incognito\n90,Iron Maiden\n91,James Brown\n92,Jamiroquai\n93,JET\n94,Jimi Hendrix\n95,Joe Satriani\n96,Jota Quest\n97,João Suplicy\n98,Judas Priest\n99,Legião Urbana\n100,Lenny Kravitz\n101,Lulu Santos\n102,Marillion\n103,Marisa Monte\n104,Marvin Gaye\n105,Men At Work\n106,Motörhead\n107,Motörhead & Girlschool\n108,Mônica Marianno\n109,Mötley Crüe\n110,Nirvana\n111,O Terço\n112,Olodum\n113,Os Paralamas Do Sucesso\n114,Ozzy Osbourne\n115,Page & Plant\n116,Passengers\n117,Paul D'Ianno\n118,Pearl Jam\n119,Peter Tosh\n120,Pink Floyd\n121,Planet Hemp\n122,R.E.M. Feat. Kate Pearson\n123,R.E.M. Feat. KRS-One\n124,R.E.M.\n125,Raimundos\n126,Raul Seixas\n127,Red Hot Chili Peppers\n128,Rush\n129,Simply Red\n130,Skank\n131,Smashing Pumpkins\n132,Soundgarden\n133,Stevie Ray Vaughan & Double Trouble\n134,Stone Temple Pilots\n135,System Of A Down\n136,\"Terry Bozzio, Tony Levin & Steve Stevens\"\n137,The Black Crowes\n138,The Clash\n139,The Cult\n140,The Doors\n141,The Police\n142,The Rolling Stones\n143,The Tea Party\n144,The Who\n145,Tim Maia\n146,Titãs\n147,Battlestar Galactica\n148,Heroes\n149,Lost\n150,U2\n151,UB40\n152,Van Halen\n153,Velvet Revolver\n154,Whitesnake\n155,Zeca Pagodinho\n156,The Office\n157,Dread Zeppelin\n158,Battlestar Galactica (Classic)\n159,Aquaman\n160,Christina Aguilera featuring BigElf\n161,Aerosmith & Sierra Leone's Refugee Allstars\n162,Los Lonely Boys\n163,Corinne Bailey Rae\n164,Dhani Harrison & Jakob Dylan\n165,Jackson Browne\n166,Avril Lavigne\n167,Big & Rich\n168,Youssou N'Dour\n169,Black Eyed Peas\n170,Jack Johnson\n171,Ben Harper\n172,Snow Patrol\n173,Matisyahu\n174,The Postal Service\n175,Jaguares\n176,The Flaming Lips\n177,Jack's Mannequin & Mick Fleetwood\n178,Regina Spektor\n179,Scorpions\n180,House Of Pain\n181,Xis\n182,Nega Gizza\n183,Gustavo & Andres Veiga & Salazar\n184,Rodox\n185,Charlie Brown Jr.\n186,Pedro Luís E A Parede\n187,Los Hermanos\n188,Mundo Livre S/A\n189,Otto\n190,Instituto\n191,Nação Zumbi\n192,DJ Dolores & Orchestra Santa Massa\n193,Seu Jorge\n194,Sabotage E Instituto\n195,Stereo Maracana\n196,Cake\n197,Aisha Duo\n198,Habib Koité and Bamada\n199,Karsh Kale\n200,The Posies\n201,Luciana Souza/Romero Lubambo\n202,Aaron Goldberg\n203,Nicolaus Esterhazy Sinfonia\n204,Temple of the Dog\n205,Chris Cornell\n206,Alberto Turco & Nova Schola Gregoriana\n207,\"Richard Marlow & The Choir of Trinity College, Cambridge\"\n208,English Concert & Trevor Pinnock\n209,\"Anne-Sophie Mutter, Herbert Von Karajan & Wiener Philharmoniker\"\n210,\"Hilary Hahn, Jeffrey Kahane, Los Angeles Chamber Orchestra & Margaret Batjer\"\n211,Wilhelm Kempff\n212,Yo-Yo Ma\n213,Scholars Baroque Ensemble\n214,Academy of St. Martin in the Fields & Sir Neville Marriner\n215,Academy of St. Martin in the Fields Chamber Ensemble & Sir Neville Marriner\n216,\"Berliner Philharmoniker, Claudio Abbado & Sabine Meyer\"\n217,Royal Philharmonic Orchestra & Sir Thomas Beecham\n218,Orchestre Révolutionnaire et Romantique & John Eliot Gardiner\n219,\"Britten Sinfonia, Ivor Bolton & Lesley Garrett\"\n220,\"Chicago Symphony Chorus, Chicago Symphony Orchestra & Sir Georg Solti\"\n221,Sir Georg Solti & Wiener Philharmoniker\n222,\"Academy of St. Martin in the Fields, John Birch, Sir Neville Marriner & Sylvia McNair\"\n223,London Symphony Orchestra & Sir Charles Mackerras\n224,Barry Wordsworth & BBC Concert Orchestra\n225,\"Herbert Von Karajan, Mirella Freni & Wiener Philharmoniker\"\n226,Eugene Ormandy\n227,Luciano Pavarotti\n228,Leonard Bernstein & New York Philharmonic\n229,Boston Symphony Orchestra & Seiji Ozawa\n230,Aaron Copland & London Symphony Orchestra\n231,Ton Koopman\n232,Sergei Prokofiev & Yuri Temirkanov\n233,Chicago Symphony Orchestra & Fritz Reiner\n234,Orchestra of The Age of Enlightenment\n235,\"Emanuel Ax, Eugene Ormandy & Philadelphia Orchestra\"\n236,James Levine\n237,Berliner Philharmoniker & Hans Rosbaud\n238,Maurizio Pollini\n239,\"Academy of St. Martin in the Fields, Sir Neville Marriner & William Bennett\"\n240,Gustav Mahler\n241,\"Felix Schmidt, London Symphony Orchestra & Rafael Frühbeck de Burgos\"\n242,Edo de Waart & San Francisco Symphony\n243,Antal Doráti & London Symphony Orchestra\n244,Choir Of Westminster Abbey & Simon Preston\n245,Michael Tilson Thomas & San Francisco Symphony\n246,\"Chor der Wiener Staatsoper, Herbert Von Karajan & Wiener Philharmoniker\"\n247,The King's Singers\n248,Berliner Philharmoniker & Herbert Von Karajan\n249,\"Sir Georg Solti, Sumi Jo & Wiener Philharmoniker\"\n250,Christopher O'Riley\n251,Fretwork\n252,Amy Winehouse\n253,Calexico\n254,Otto Klemperer & Philharmonia Orchestra\n255,Yehudi Menuhin\n256,Philharmonia Orchestra & Sir Neville Marriner\n257,\"Academy of St. Martin in the Fields, Sir Neville Marriner & Thurston Dart\"\n258,Les Arts Florissants & William Christie\n259,The 12 Cellists of The Berlin Philharmonic\n260,Adrian Leaper & Doreen de Feis\n261,\"Roger Norrington, London Classical Players\"\n262,Charles Dutoit & L'Orchestre Symphonique de Montréal\n263,\"Equale Brass Ensemble, John Eliot Gardiner & Munich Monteverdi Orchestra and Choir\"\n264,Kent Nagano and Orchestre de l'Opéra de Lyon\n265,Julian Bream\n266,Martin Roscoe\n267,Göteborgs Symfoniker & Neeme Järvi\n268,Itzhak Perlman\n269,Michele Campanella\n270,Gerald Moore\n271,\"Mela Tenenbaum, Pro Musica Prague & Richard Kapp\"\n272,Emerson String Quartet\n273,\"C. Monteverdi, Nigel Rogers - Chiaroscuro; London Baroque; London Cornett & Sackbu\"\n274,Nash Ensemble\n275,Philip Glass Ensemble\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/customers.csv",
    "content": "customer_id,first_name,last_name,company,address,city,state,country,postal_code,phone,fax,email,support_rep_id\n1,Luís,Gonçalves,Embraer - Empresa Brasileira de Aeronáutica S.A.,\"Av. Brigadeiro Faria Lima, 2170\",São José dos Campos,SP,Brazil,12227-000,+55 (12) 3923-5555,+55 (12) 3923-5566,luisg@embraer.com.br,3\n2,Leonie,Köhler,,Theodor-Heuss-Straße 34,Stuttgart,,Germany,70174,+49 0711 2842222,,leonekohler@surfeu.de,5\n3,François,Tremblay,,1498 rue Bélanger,Montréal,QC,Canada,H2G 1A7,+1 (514) 721-4711,,ftremblay@gmail.com,3\n4,Bjørn,Hansen,,Ullevålsveien 14,Oslo,,Norway,0171,+47 22 44 22 22,,bjorn.hansen@yahoo.no,4\n5,František,Wichterlová,JetBrains s.r.o.,Klanova 9/506,Prague,,Czech Republic,14700,+420 2 4172 5555,+420 2 4172 5555,frantisekw@jetbrains.com,4\n6,Helena,Holý,,Rilská 3174/6,Prague,,Czech Republic,14300,+420 2 4177 0449,,hholy@gmail.com,5\n7,Astrid,Gruber,,\"Rotenturmstraße 4, 1010 Innere Stadt\",Vienne,,Austria,1010,+43 01 5134505,,astrid.gruber@apple.at,5\n8,Daan,Peeters,,Grétrystraat 63,Brussels,,Belgium,1000,+32 02 219 03 03,,daan_peeters@apple.be,4\n9,Kara,Nielsen,,Sønder Boulevard 51,Copenhagen,,Denmark,1720,+453 3331 9991,,kara.nielsen@jubii.dk,4\n10,Eduardo,Martins,Woodstock Discos,\"Rua Dr. Falcão Filho, 155\",São Paulo,SP,Brazil,01007-010,+55 (11) 3033-5446,+55 (11) 3033-4564,eduardo@woodstock.com.br,4\n11,Alexandre,Rocha,Banco do Brasil S.A.,\"Av. Paulista, 2022\",São Paulo,SP,Brazil,01310-200,+55 (11) 3055-3278,+55 (11) 3055-8131,alero@uol.com.br,5\n12,Roberto,Almeida,Riotur,\"Praça Pio X, 119\",Rio de Janeiro,RJ,Brazil,20040-020,+55 (21) 2271-7000,+55 (21) 2271-7070,roberto.almeida@riotur.gov.br,3\n13,Fernanda,Ramos,,Qe 7 Bloco G,Brasília,DF,Brazil,71020-677,+55 (61) 3363-5547,+55 (61) 3363-7855,fernadaramos4@uol.com.br,4\n14,Mark,Philips,Telus,8210 111 ST NW,Edmonton,AB,Canada,T6G 2C7,+1 (780) 434-4554,+1 (780) 434-5565,mphilips12@shaw.ca,5\n15,Jennifer,Peterson,Rogers Canada,700 W Pender Street,Vancouver,BC,Canada,V6C 1G8,+1 (604) 688-2255,+1 (604) 688-8756,jenniferp@rogers.ca,3\n16,Frank,Harris,Google Inc.,1600 Amphitheatre Parkway,Mountain View,CA,USA,94043-1351,+1 (650) 253-0000,+1 (650) 253-0000,fharris@google.com,4\n17,Jack,Smith,Microsoft Corporation,1 Microsoft Way,Redmond,WA,USA,98052-8300,+1 (425) 882-8080,+1 (425) 882-8081,jacksmith@microsoft.com,5\n18,Michelle,Brooks,,627 Broadway,New York,NY,USA,10012-2612,+1 (212) 221-3546,+1 (212) 221-4679,michelleb@aol.com,3\n19,Tim,Goyer,Apple Inc.,1 Infinite Loop,Cupertino,CA,USA,95014,+1 (408) 996-1010,+1 (408) 996-1011,tgoyer@apple.com,3\n20,Dan,Miller,,541 Del Medio Avenue,Mountain View,CA,USA,94040-111,+1 (650) 644-3358,,dmiller@comcast.com,4\n21,Kathy,Chase,,801 W 4th Street,Reno,NV,USA,89503,+1 (775) 223-7665,,kachase@hotmail.com,5\n22,Heather,Leacock,,120 S Orange Ave,Orlando,FL,USA,32801,+1 (407) 999-7788,,hleacock@gmail.com,4\n23,John,Gordon,,69 Salem Street,Boston,MA,USA,2113,+1 (617) 522-1333,,johngordon22@yahoo.com,4\n24,Frank,Ralston,,162 E Superior Street,Chicago,IL,USA,60611,+1 (312) 332-3232,,fralston@gmail.com,3\n25,Victor,Stevens,,319 N. Frances Street,Madison,WI,USA,53703,+1 (608) 257-0597,,vstevens@yahoo.com,5\n26,Richard,Cunningham,,2211 W Berry Street,Fort Worth,TX,USA,76110,+1 (817) 924-7272,,ricunningham@hotmail.com,4\n27,Patrick,Gray,,1033 N Park Ave,Tucson,AZ,USA,85719,+1 (520) 622-4200,,patrick.gray@aol.com,4\n28,Julia,Barnett,,302 S 700 E,Salt Lake City,UT,USA,84102,+1 (801) 531-7272,,jubarnett@gmail.com,5\n29,Robert,Brown,,796 Dundas Street West,Toronto,ON,Canada,M6J 1V1,+1 (416) 363-8888,,robbrown@shaw.ca,3\n30,Edward,Francis,,230 Elgin Street,Ottawa,ON,Canada,K2P 1L7,+1 (613) 234-3322,,edfrancis@yachoo.ca,3\n31,Martha,Silk,,194A Chain Lake Drive,Halifax,NS,Canada,B3S 1C5,+1 (902) 450-0450,,marthasilk@gmail.com,5\n32,Aaron,Mitchell,,696 Osborne Street,Winnipeg,MB,Canada,R3L 2B9,+1 (204) 452-6452,,aaronmitchell@yahoo.ca,4\n33,Ellie,Sullivan,,5112 48 Street,Yellowknife,NT,Canada,X1A 1N6,+1 (867) 920-2233,,ellie.sullivan@shaw.ca,3\n34,João,Fernandes,,Rua da Assunção 53,Lisbon,,Portugal,,+351 (213) 466-111,,jfernandes@yahoo.pt,4\n35,Madalena,Sampaio,,\"Rua dos Campeões Europeus de Viena, 4350\",Porto,,Portugal,,+351 (225) 022-448,,masampaio@sapo.pt,4\n36,Hannah,Schneider,,Tauentzienstraße 8,Berlin,,Germany,10789,+49 030 26550280,,hannah.schneider@yahoo.de,5\n37,Fynn,Zimmermann,,Berger Straße 10,Frankfurt,,Germany,60316,+49 069 40598889,,fzimmermann@yahoo.de,3\n38,Niklas,Schröder,,Barbarossastraße 19,Berlin,,Germany,10779,+49 030 2141444,,nschroder@surfeu.de,3\n39,Camille,Bernard,,\"4, Rue Milton\",Paris,,France,75009,+33 01 49 70 65 65,,camille.bernard@yahoo.fr,4\n40,Dominique,Lefebvre,,\"8, Rue Hanovre\",Paris,,France,75002,+33 01 47 42 71 71,,dominiquelefebvre@gmail.com,4\n41,Marc,Dubois,,\"11, Place Bellecour\",Lyon,,France,69002,+33 04 78 30 30 30,,marc.dubois@hotmail.com,5\n42,Wyatt,Girard,,\"9, Place Louis Barthou\",Bordeaux,,France,33000,+33 05 56 96 96 96,,wyatt.girard@yahoo.fr,3\n43,Isabelle,Mercier,,\"68, Rue Jouvence\",Dijon,,France,21000,+33 03 80 73 66 99,,isabelle_mercier@apple.fr,3\n44,Terhi,Hämäläinen,,Porthaninkatu 9,Helsinki,,Finland,00530,+358 09 870 2000,,terhi.hamalainen@apple.fi,3\n45,Ladislav,Kovács,,Erzsébet krt. 58.,Budapest,,Hungary,H-1073,,,ladislav_kovacs@apple.hu,3\n46,Hugh,O'Reilly,,3 Chatham Street,Dublin,Dublin,Ireland,,+353 01 6792424,,hughoreilly@apple.ie,3\n47,Lucas,Mancini,,\"Via Degli Scipioni, 43\",Rome,RM,Italy,00192,+39 06 39733434,,lucas.mancini@yahoo.it,5\n48,Johannes,Van der Berg,,Lijnbaansgracht 120bg,Amsterdam,VV,Netherlands,1016,+31 020 6223130,,johavanderberg@yahoo.nl,5\n49,Stanisław,Wójcik,,Ordynacka 10,Warsaw,,Poland,00-358,+48 22 828 37 39,,stanisław.wójcik@wp.pl,4\n50,Enrique,Muñoz,,C/ San Bernardo 85,Madrid,,Spain,28015,+34 914 454 454,,enrique_munoz@yahoo.es,5\n51,Joakim,Johansson,,Celsiusg. 9,Stockholm,,Sweden,11230,+46 08-651 52 52,,joakim.johansson@yahoo.se,5\n52,Emma,Jones,,202 Hoxton Street,London,,United Kingdom,N1 5LH,+44 020 7707 0707,,emma_jones@hotmail.com,3\n53,Phil,Hughes,,113 Lupus St,London,,United Kingdom,SW1V 3EN,+44 020 7976 5722,,phil.hughes@gmail.com,3\n54,Steve,Murray,,110 Raeburn Pl,Edinburgh ,,United Kingdom,EH4 1HH,+44 0131 315 3300,,steve.murray@yahoo.uk,5\n55,Mark,Taylor,,421 Bourke Street,Sidney,NSW,Australia,2010,+61 (02) 9332 3633,,mark.taylor@yahoo.au,4\n56,Diego,Gutiérrez,,307 Macacha Güemes,Buenos Aires,,Argentina,1106,+54 (0)11 4311 4333,,diego.gutierrez@yahoo.ar,4\n57,Luis,Rojas,,\"Calle Lira, 198\",Santiago,,Chile,,+56 (0)2 635 4444,,luisrojas@yahoo.cl,5\n58,Manoj,Pareek,,\"12,Community Centre\",Delhi,,India,110017,+91 0124 39883988,,manoj.pareek@rediff.com,3\n59,Puja,Srivastava,,\"3,Raj Bhavan Road\",Bangalore,,India,560001,+91 080 22289999,,puja_srivastava@yahoo.in,3\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/employees.csv",
    "content": "employee_id,last_name,first_name,title,reports_to,birth_date,hire_date,address,city,state,country,postal_code,phone,fax,email\n1,Adams,Andrew,General Manager,6,1962-02-18T00:00:00.000000000,2002-08-14T00:00:00.000000000,11120 Jasper Ave NW,Edmonton,AB,Canada,T5K 2N1,+1 (780) 428-9482,+1 (780) 428-3457,andrew@chinookcorp.com\n2,Edwards,Nancy,Sales Manager,1,1958-12-08T00:00:00.000000000,2002-05-01T00:00:00.000000000,825 8 Ave SW,Calgary,AB,Canada,T2P 2T3,+1 (403) 262-3443,+1 (403) 262-3322,nancy@chinookcorp.com\n3,Peacock,Jane,Sales Support Agent,2,1973-08-29T00:00:00.000000000,2002-04-01T00:00:00.000000000,1111 6 Ave SW,Calgary,AB,Canada,T2P 5M5,+1 (403) 262-3443,+1 (403) 262-6712,jane@chinookcorp.com\n4,Park,Margaret,Sales Support Agent,2,1947-09-19T00:00:00.000000000,2003-05-03T00:00:00.000000000,683 10 Street SW,Calgary,AB,Canada,T2P 5G3,+1 (403) 263-4423,+1 (403) 263-4289,margaret@chinookcorp.com\n5,Johnson,Steve,Sales Support Agent,2,1965-03-03T00:00:00.000000000,2003-10-17T00:00:00.000000000,7727B 41 Ave,Calgary,AB,Canada,T3B 1Y7,1 (780) 836-9987,1 (780) 836-9543,steve@chinookcorp.com\n6,Mitchell,Michael,IT Manager,1,1973-07-01T00:00:00.000000000,2003-10-17T00:00:00.000000000,5827 Bowness Road NW,Calgary,AB,Canada,T3B 0C5,+1 (403) 246-9887,+1 (403) 246-9899,michael@chinookcorp.com\n7,King,Robert,IT Staff,6,1970-05-29T00:00:00.000000000,2004-01-02T00:00:00.000000000,590 Columbia Boulevard West,Lethbridge,AB,Canada,T1K 5N8,+1 (403) 456-9986,+1 (403) 456-8485,robert@chinookcorp.com\n8,Callahan,Laura,IT Staff,6,1968-01-09T00:00:00.000000000,2004-03-04T00:00:00.000000000,923 7 ST NW,Lethbridge,AB,Canada,T1H 1Y8,+1 (403) 467-3351,+1 (403) 467-8772,laura@chinookcorp.com\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/genres.csv",
    "content": "genre_id,name\n1,Rock\n2,Jazz\n3,Metal\n4,Alternative & Punk\n5,Rock And Roll\n6,Blues\n7,Latin\n8,Reggae\n9,Pop\n10,Soundtrack\n11,Bossa Nova\n12,Easy Listening\n13,Heavy Metal\n14,R&B/Soul\n15,Electronica/Dance\n16,World\n17,Hip Hop/Rap\n18,Science Fiction\n19,TV Shows\n20,Sci Fi & Fantasy\n21,Drama\n22,Comedy\n23,Alternative\n24,Classical\n25,Opera\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/invoice_items.csv",
    "content": "invoice_line_id,invoice_id,track_id,unit_price,quantity\n1,1,2,0.99,1\n2,1,4,0.99,1\n3,2,6,0.99,1\n4,2,8,0.99,1\n5,2,10,0.99,1\n6,2,12,0.99,1\n7,3,16,0.99,1\n8,3,20,0.99,1\n9,3,24,0.99,1\n10,3,28,0.99,1\n11,3,32,0.99,1\n12,3,36,0.99,1\n13,4,42,0.99,1\n14,4,48,0.99,1\n15,4,54,0.99,1\n16,4,60,0.99,1\n17,4,66,0.99,1\n18,4,72,0.99,1\n19,4,78,0.99,1\n20,4,84,0.99,1\n21,4,90,0.99,1\n22,5,99,0.99,1\n23,5,108,0.99,1\n24,5,117,0.99,1\n25,5,126,0.99,1\n26,5,135,0.99,1\n27,5,144,0.99,1\n28,5,153,0.99,1\n29,5,162,0.99,1\n30,5,171,0.99,1\n31,5,180,0.99,1\n32,5,189,0.99,1\n33,5,198,0.99,1\n34,5,207,0.99,1\n35,5,216,0.99,1\n36,6,230,0.99,1\n37,7,231,0.99,1\n38,7,232,0.99,1\n39,8,234,0.99,1\n40,8,236,0.99,1\n41,9,238,0.99,1\n42,9,240,0.99,1\n43,9,242,0.99,1\n44,9,244,0.99,1\n45,10,248,0.99,1\n46,10,252,0.99,1\n47,10,256,0.99,1\n48,10,260,0.99,1\n49,10,264,0.99,1\n50,10,268,0.99,1\n51,11,274,0.99,1\n52,11,280,0.99,1\n53,11,286,0.99,1\n54,11,292,0.99,1\n55,11,298,0.99,1\n56,11,304,0.99,1\n57,11,310,0.99,1\n58,11,316,0.99,1\n59,11,322,0.99,1\n60,12,331,0.99,1\n61,12,340,0.99,1\n62,12,349,0.99,1\n63,12,358,0.99,1\n64,12,367,0.99,1\n65,12,376,0.99,1\n66,12,385,0.99,1\n67,12,394,0.99,1\n68,12,403,0.99,1\n69,12,412,0.99,1\n70,12,421,0.99,1\n71,12,430,0.99,1\n72,12,439,0.99,1\n73,12,448,0.99,1\n74,13,462,0.99,1\n75,14,463,0.99,1\n76,14,464,0.99,1\n77,15,466,0.99,1\n78,15,468,0.99,1\n79,16,470,0.99,1\n80,16,472,0.99,1\n81,16,474,0.99,1\n82,16,476,0.99,1\n83,17,480,0.99,1\n84,17,484,0.99,1\n85,17,488,0.99,1\n86,17,492,0.99,1\n87,17,496,0.99,1\n88,17,500,0.99,1\n89,18,506,0.99,1\n90,18,512,0.99,1\n91,18,518,0.99,1\n92,18,524,0.99,1\n93,18,530,0.99,1\n94,18,536,0.99,1\n95,18,542,0.99,1\n96,18,548,0.99,1\n97,18,554,0.99,1\n98,19,563,0.99,1\n99,19,572,0.99,1\n100,19,581,0.99,1\n101,19,590,0.99,1\n102,19,599,0.99,1\n103,19,608,0.99,1\n104,19,617,0.99,1\n105,19,626,0.99,1\n106,19,635,0.99,1\n107,19,644,0.99,1\n108,19,653,0.99,1\n109,19,662,0.99,1\n110,19,671,0.99,1\n111,19,680,0.99,1\n112,20,694,0.99,1\n113,21,695,0.99,1\n114,21,696,0.99,1\n115,22,698,0.99,1\n116,22,700,0.99,1\n117,23,702,0.99,1\n118,23,704,0.99,1\n119,23,706,0.99,1\n120,23,708,0.99,1\n121,24,712,0.99,1\n122,24,716,0.99,1\n123,24,720,0.99,1\n124,24,724,0.99,1\n125,24,728,0.99,1\n126,24,732,0.99,1\n127,25,738,0.99,1\n128,25,744,0.99,1\n129,25,750,0.99,1\n130,25,756,0.99,1\n131,25,762,0.99,1\n132,25,768,0.99,1\n133,25,774,0.99,1\n134,25,780,0.99,1\n135,25,786,0.99,1\n136,26,795,0.99,1\n137,26,804,0.99,1\n138,26,813,0.99,1\n139,26,822,0.99,1\n140,26,831,0.99,1\n141,26,840,0.99,1\n142,26,849,0.99,1\n143,26,858,0.99,1\n144,26,867,0.99,1\n145,26,876,0.99,1\n146,26,885,0.99,1\n147,26,894,0.99,1\n148,26,903,0.99,1\n149,26,912,0.99,1\n150,27,926,0.99,1\n151,28,927,0.99,1\n152,28,928,0.99,1\n153,29,930,0.99,1\n154,29,932,0.99,1\n155,30,934,0.99,1\n156,30,936,0.99,1\n157,30,938,0.99,1\n158,30,940,0.99,1\n159,31,944,0.99,1\n160,31,948,0.99,1\n161,31,952,0.99,1\n162,31,956,0.99,1\n163,31,960,0.99,1\n164,31,964,0.99,1\n165,32,970,0.99,1\n166,32,976,0.99,1\n167,32,982,0.99,1\n168,32,988,0.99,1\n169,32,994,0.99,1\n170,32,1000,0.99,1\n171,32,1006,0.99,1\n172,32,1012,0.99,1\n173,32,1018,0.99,1\n174,33,1027,0.99,1\n175,33,1036,0.99,1\n176,33,1045,0.99,1\n177,33,1054,0.99,1\n178,33,1063,0.99,1\n179,33,1072,0.99,1\n180,33,1081,0.99,1\n181,33,1090,0.99,1\n182,33,1099,0.99,1\n183,33,1108,0.99,1\n184,33,1117,0.99,1\n185,33,1126,0.99,1\n186,33,1135,0.99,1\n187,33,1144,0.99,1\n188,34,1158,0.99,1\n189,35,1159,0.99,1\n190,35,1160,0.99,1\n191,36,1162,0.99,1\n192,36,1164,0.99,1\n193,37,1166,0.99,1\n194,37,1168,0.99,1\n195,37,1170,0.99,1\n196,37,1172,0.99,1\n197,38,1176,0.99,1\n198,38,1180,0.99,1\n199,38,1184,0.99,1\n200,38,1188,0.99,1\n201,38,1192,0.99,1\n202,38,1196,0.99,1\n203,39,1202,0.99,1\n204,39,1208,0.99,1\n205,39,1214,0.99,1\n206,39,1220,0.99,1\n207,39,1226,0.99,1\n208,39,1232,0.99,1\n209,39,1238,0.99,1\n210,39,1244,0.99,1\n211,39,1250,0.99,1\n212,40,1259,0.99,1\n213,40,1268,0.99,1\n214,40,1277,0.99,1\n215,40,1286,0.99,1\n216,40,1295,0.99,1\n217,40,1304,0.99,1\n218,40,1313,0.99,1\n219,40,1322,0.99,1\n220,40,1331,0.99,1\n221,40,1340,0.99,1\n222,40,1349,0.99,1\n223,40,1358,0.99,1\n224,40,1367,0.99,1\n225,40,1376,0.99,1\n226,41,1390,0.99,1\n227,42,1391,0.99,1\n228,42,1392,0.99,1\n229,43,1394,0.99,1\n230,43,1396,0.99,1\n231,44,1398,0.99,1\n232,44,1400,0.99,1\n233,44,1402,0.99,1\n234,44,1404,0.99,1\n235,45,1408,0.99,1\n236,45,1412,0.99,1\n237,45,1416,0.99,1\n238,45,1420,0.99,1\n239,45,1424,0.99,1\n240,45,1428,0.99,1\n241,46,1434,0.99,1\n242,46,1440,0.99,1\n243,46,1446,0.99,1\n244,46,1452,0.99,1\n245,46,1458,0.99,1\n246,46,1464,0.99,1\n247,46,1470,0.99,1\n248,46,1476,0.99,1\n249,46,1482,0.99,1\n250,47,1491,0.99,1\n251,47,1500,0.99,1\n252,47,1509,0.99,1\n253,47,1518,0.99,1\n254,47,1527,0.99,1\n255,47,1536,0.99,1\n256,47,1545,0.99,1\n257,47,1554,0.99,1\n258,47,1563,0.99,1\n259,47,1572,0.99,1\n260,47,1581,0.99,1\n261,47,1590,0.99,1\n262,47,1599,0.99,1\n263,47,1608,0.99,1\n264,48,1622,0.99,1\n265,49,1623,0.99,1\n266,49,1624,0.99,1\n267,50,1626,0.99,1\n268,50,1628,0.99,1\n269,51,1630,0.99,1\n270,51,1632,0.99,1\n271,51,1634,0.99,1\n272,51,1636,0.99,1\n273,52,1640,0.99,1\n274,52,1644,0.99,1\n275,52,1648,0.99,1\n276,52,1652,0.99,1\n277,52,1656,0.99,1\n278,52,1660,0.99,1\n279,53,1666,0.99,1\n280,53,1672,0.99,1\n281,53,1678,0.99,1\n282,53,1684,0.99,1\n283,53,1690,0.99,1\n284,53,1696,0.99,1\n285,53,1702,0.99,1\n286,53,1708,0.99,1\n287,53,1714,0.99,1\n288,54,1723,0.99,1\n289,54,1732,0.99,1\n290,54,1741,0.99,1\n291,54,1750,0.99,1\n292,54,1759,0.99,1\n293,54,1768,0.99,1\n294,54,1777,0.99,1\n295,54,1786,0.99,1\n296,54,1795,0.99,1\n297,54,1804,0.99,1\n298,54,1813,0.99,1\n299,54,1822,0.99,1\n300,54,1831,0.99,1\n301,54,1840,0.99,1\n302,55,1854,0.99,1\n303,56,1855,0.99,1\n304,56,1856,0.99,1\n305,57,1858,0.99,1\n306,57,1860,0.99,1\n307,58,1862,0.99,1\n308,58,1864,0.99,1\n309,58,1866,0.99,1\n310,58,1868,0.99,1\n311,59,1872,0.99,1\n312,59,1876,0.99,1\n313,59,1880,0.99,1\n314,59,1884,0.99,1\n315,59,1888,0.99,1\n316,59,1892,0.99,1\n317,60,1898,0.99,1\n318,60,1904,0.99,1\n319,60,1910,0.99,1\n320,60,1916,0.99,1\n321,60,1922,0.99,1\n322,60,1928,0.99,1\n323,60,1934,0.99,1\n324,60,1940,0.99,1\n325,60,1946,0.99,1\n326,61,1955,0.99,1\n327,61,1964,0.99,1\n328,61,1973,0.99,1\n329,61,1982,0.99,1\n330,61,1991,0.99,1\n331,61,2000,0.99,1\n332,61,2009,0.99,1\n333,61,2018,0.99,1\n334,61,2027,0.99,1\n335,61,2036,0.99,1\n336,61,2045,0.99,1\n337,61,2054,0.99,1\n338,61,2063,0.99,1\n339,61,2072,0.99,1\n340,62,2086,0.99,1\n341,63,2087,0.99,1\n342,63,2088,0.99,1\n343,64,2090,0.99,1\n344,64,2092,0.99,1\n345,65,2094,0.99,1\n346,65,2096,0.99,1\n347,65,2098,0.99,1\n348,65,2100,0.99,1\n349,66,2104,0.99,1\n350,66,2108,0.99,1\n351,66,2112,0.99,1\n352,66,2116,0.99,1\n353,66,2120,0.99,1\n354,66,2124,0.99,1\n355,67,2130,0.99,1\n356,67,2136,0.99,1\n357,67,2142,0.99,1\n358,67,2148,0.99,1\n359,67,2154,0.99,1\n360,67,2160,0.99,1\n361,67,2166,0.99,1\n362,67,2172,0.99,1\n363,67,2178,0.99,1\n364,68,2187,0.99,1\n365,68,2196,0.99,1\n366,68,2205,0.99,1\n367,68,2214,0.99,1\n368,68,2223,0.99,1\n369,68,2232,0.99,1\n370,68,2241,0.99,1\n371,68,2250,0.99,1\n372,68,2259,0.99,1\n373,68,2268,0.99,1\n374,68,2277,0.99,1\n375,68,2286,0.99,1\n376,68,2295,0.99,1\n377,68,2304,0.99,1\n378,69,2318,0.99,1\n379,70,2319,0.99,1\n380,70,2320,0.99,1\n381,71,2322,0.99,1\n382,71,2324,0.99,1\n383,72,2326,0.99,1\n384,72,2328,0.99,1\n385,72,2330,0.99,1\n386,72,2332,0.99,1\n387,73,2336,0.99,1\n388,73,2340,0.99,1\n389,73,2344,0.99,1\n390,73,2348,0.99,1\n391,73,2352,0.99,1\n392,73,2356,0.99,1\n393,74,2362,0.99,1\n394,74,2368,0.99,1\n395,74,2374,0.99,1\n396,74,2380,0.99,1\n397,74,2386,0.99,1\n398,74,2392,0.99,1\n399,74,2398,0.99,1\n400,74,2404,0.99,1\n401,74,2410,0.99,1\n402,75,2419,0.99,1\n403,75,2428,0.99,1\n404,75,2437,0.99,1\n405,75,2446,0.99,1\n406,75,2455,0.99,1\n407,75,2464,0.99,1\n408,75,2473,0.99,1\n409,75,2482,0.99,1\n410,75,2491,0.99,1\n411,75,2500,0.99,1\n412,75,2509,0.99,1\n413,75,2518,0.99,1\n414,75,2527,0.99,1\n415,75,2536,0.99,1\n416,76,2550,0.99,1\n417,77,2551,0.99,1\n418,77,2552,0.99,1\n419,78,2554,0.99,1\n420,78,2556,0.99,1\n421,79,2558,0.99,1\n422,79,2560,0.99,1\n423,79,2562,0.99,1\n424,79,2564,0.99,1\n425,80,2568,0.99,1\n426,80,2572,0.99,1\n427,80,2576,0.99,1\n428,80,2580,0.99,1\n429,80,2584,0.99,1\n430,80,2588,0.99,1\n431,81,2594,0.99,1\n432,81,2600,0.99,1\n433,81,2606,0.99,1\n434,81,2612,0.99,1\n435,81,2618,0.99,1\n436,81,2624,0.99,1\n437,81,2630,0.99,1\n438,81,2636,0.99,1\n439,81,2642,0.99,1\n440,82,2651,0.99,1\n441,82,2660,0.99,1\n442,82,2669,0.99,1\n443,82,2678,0.99,1\n444,82,2687,0.99,1\n445,82,2696,0.99,1\n446,82,2705,0.99,1\n447,82,2714,0.99,1\n448,82,2723,0.99,1\n449,82,2732,0.99,1\n450,82,2741,0.99,1\n451,82,2750,0.99,1\n452,82,2759,0.99,1\n453,82,2768,0.99,1\n454,83,2782,0.99,1\n455,84,2783,0.99,1\n456,84,2784,0.99,1\n457,85,2786,0.99,1\n458,85,2788,0.99,1\n459,86,2790,0.99,1\n460,86,2792,0.99,1\n461,86,2794,0.99,1\n462,86,2796,0.99,1\n463,87,2800,0.99,1\n464,87,2804,0.99,1\n465,87,2808,0.99,1\n466,87,2812,0.99,1\n467,87,2816,0.99,1\n468,87,2820,1.99,1\n469,88,2826,1.99,1\n470,88,2832,1.99,1\n471,88,2838,1.99,1\n472,88,2844,1.99,1\n473,88,2850,1.99,1\n474,88,2856,1.99,1\n475,88,2862,1.99,1\n476,88,2868,1.99,1\n477,88,2874,1.99,1\n478,89,2883,1.99,1\n479,89,2892,1.99,1\n480,89,2901,1.99,1\n481,89,2910,1.99,1\n482,89,2919,1.99,1\n483,89,2928,0.99,1\n484,89,2937,0.99,1\n485,89,2946,0.99,1\n486,89,2955,0.99,1\n487,89,2964,0.99,1\n488,89,2973,0.99,1\n489,89,2982,0.99,1\n490,89,2991,0.99,1\n491,89,3000,0.99,1\n492,90,3014,0.99,1\n493,91,3015,0.99,1\n494,91,3016,0.99,1\n495,92,3018,0.99,1\n496,92,3020,0.99,1\n497,93,3022,0.99,1\n498,93,3024,0.99,1\n499,93,3026,0.99,1\n500,93,3028,0.99,1\n501,94,3032,0.99,1\n502,94,3036,0.99,1\n503,94,3040,0.99,1\n504,94,3044,0.99,1\n505,94,3048,0.99,1\n506,94,3052,0.99,1\n507,95,3058,0.99,1\n508,95,3064,0.99,1\n509,95,3070,0.99,1\n510,95,3076,0.99,1\n511,95,3082,0.99,1\n512,95,3088,0.99,1\n513,95,3094,0.99,1\n514,95,3100,0.99,1\n515,95,3106,0.99,1\n516,96,3115,0.99,1\n517,96,3124,0.99,1\n518,96,3133,0.99,1\n519,96,3142,0.99,1\n520,96,3151,0.99,1\n521,96,3160,0.99,1\n522,96,3169,1.99,1\n523,96,3178,1.99,1\n524,96,3187,1.99,1\n525,96,3196,1.99,1\n526,96,3205,1.99,1\n527,96,3214,1.99,1\n528,96,3223,1.99,1\n529,96,3232,1.99,1\n530,97,3246,1.99,1\n531,98,3247,1.99,1\n532,98,3248,1.99,1\n533,99,3250,1.99,1\n534,99,3252,1.99,1\n535,100,3254,0.99,1\n536,100,3256,0.99,1\n537,100,3258,0.99,1\n538,100,3260,0.99,1\n539,101,3264,0.99,1\n540,101,3268,0.99,1\n541,101,3272,0.99,1\n542,101,3276,0.99,1\n543,101,3280,0.99,1\n544,101,3284,0.99,1\n545,102,3290,0.99,1\n546,102,3296,0.99,1\n547,102,3302,0.99,1\n548,102,3308,0.99,1\n549,102,3314,0.99,1\n550,102,3320,0.99,1\n551,102,3326,0.99,1\n552,102,3332,0.99,1\n553,102,3338,1.99,1\n554,103,3347,1.99,1\n555,103,3356,0.99,1\n556,103,3365,0.99,1\n557,103,3374,0.99,1\n558,103,3383,0.99,1\n559,103,3392,0.99,1\n560,103,3401,0.99,1\n561,103,3410,0.99,1\n562,103,3419,0.99,1\n563,103,3428,1.99,1\n564,103,3437,0.99,1\n565,103,3446,0.99,1\n566,103,3455,0.99,1\n567,103,3464,0.99,1\n568,104,3478,0.99,1\n569,105,3479,0.99,1\n570,105,3480,0.99,1\n571,106,3482,0.99,1\n572,106,3484,0.99,1\n573,107,3486,0.99,1\n574,107,3488,0.99,1\n575,107,3490,0.99,1\n576,107,3492,0.99,1\n577,108,3496,0.99,1\n578,108,3500,0.99,1\n579,108,1,0.99,1\n580,108,5,0.99,1\n581,108,9,0.99,1\n582,108,13,0.99,1\n583,109,19,0.99,1\n584,109,25,0.99,1\n585,109,31,0.99,1\n586,109,37,0.99,1\n587,109,43,0.99,1\n588,109,49,0.99,1\n589,109,55,0.99,1\n590,109,61,0.99,1\n591,109,67,0.99,1\n592,110,76,0.99,1\n593,110,85,0.99,1\n594,110,94,0.99,1\n595,110,103,0.99,1\n596,110,112,0.99,1\n597,110,121,0.99,1\n598,110,130,0.99,1\n599,110,139,0.99,1\n600,110,148,0.99,1\n601,110,157,0.99,1\n602,110,166,0.99,1\n603,110,175,0.99,1\n604,110,184,0.99,1\n605,110,193,0.99,1\n606,111,207,0.99,1\n607,112,208,0.99,1\n608,112,209,0.99,1\n609,113,211,0.99,1\n610,113,213,0.99,1\n611,114,215,0.99,1\n612,114,217,0.99,1\n613,114,219,0.99,1\n614,114,221,0.99,1\n615,115,225,0.99,1\n616,115,229,0.99,1\n617,115,233,0.99,1\n618,115,237,0.99,1\n619,115,241,0.99,1\n620,115,245,0.99,1\n621,116,251,0.99,1\n622,116,257,0.99,1\n623,116,263,0.99,1\n624,116,269,0.99,1\n625,116,275,0.99,1\n626,116,281,0.99,1\n627,116,287,0.99,1\n628,116,293,0.99,1\n629,116,299,0.99,1\n630,117,308,0.99,1\n631,117,317,0.99,1\n632,117,326,0.99,1\n633,117,335,0.99,1\n634,117,344,0.99,1\n635,117,353,0.99,1\n636,117,362,0.99,1\n637,117,371,0.99,1\n638,117,380,0.99,1\n639,117,389,0.99,1\n640,117,398,0.99,1\n641,117,407,0.99,1\n642,117,416,0.99,1\n643,117,425,0.99,1\n644,118,439,0.99,1\n645,119,440,0.99,1\n646,119,441,0.99,1\n647,120,443,0.99,1\n648,120,445,0.99,1\n649,121,447,0.99,1\n650,121,449,0.99,1\n651,121,451,0.99,1\n652,121,453,0.99,1\n653,122,457,0.99,1\n654,122,461,0.99,1\n655,122,465,0.99,1\n656,122,469,0.99,1\n657,122,473,0.99,1\n658,122,477,0.99,1\n659,123,483,0.99,1\n660,123,489,0.99,1\n661,123,495,0.99,1\n662,123,501,0.99,1\n663,123,507,0.99,1\n664,123,513,0.99,1\n665,123,519,0.99,1\n666,123,525,0.99,1\n667,123,531,0.99,1\n668,124,540,0.99,1\n669,124,549,0.99,1\n670,124,558,0.99,1\n671,124,567,0.99,1\n672,124,576,0.99,1\n673,124,585,0.99,1\n674,124,594,0.99,1\n675,124,603,0.99,1\n676,124,612,0.99,1\n677,124,621,0.99,1\n678,124,630,0.99,1\n679,124,639,0.99,1\n680,124,648,0.99,1\n681,124,657,0.99,1\n682,125,671,0.99,1\n683,126,672,0.99,1\n684,126,673,0.99,1\n685,127,675,0.99,1\n686,127,677,0.99,1\n687,128,679,0.99,1\n688,128,681,0.99,1\n689,128,683,0.99,1\n690,128,685,0.99,1\n691,129,689,0.99,1\n692,129,693,0.99,1\n693,129,697,0.99,1\n694,129,701,0.99,1\n695,129,705,0.99,1\n696,129,709,0.99,1\n697,130,715,0.99,1\n698,130,721,0.99,1\n699,130,727,0.99,1\n700,130,733,0.99,1\n701,130,739,0.99,1\n702,130,745,0.99,1\n703,130,751,0.99,1\n704,130,757,0.99,1\n705,130,763,0.99,1\n706,131,772,0.99,1\n707,131,781,0.99,1\n708,131,790,0.99,1\n709,131,799,0.99,1\n710,131,808,0.99,1\n711,131,817,0.99,1\n712,131,826,0.99,1\n713,131,835,0.99,1\n714,131,844,0.99,1\n715,131,853,0.99,1\n716,131,862,0.99,1\n717,131,871,0.99,1\n718,131,880,0.99,1\n719,131,889,0.99,1\n720,132,903,0.99,1\n721,133,904,0.99,1\n722,133,905,0.99,1\n723,134,907,0.99,1\n724,134,909,0.99,1\n725,135,911,0.99,1\n726,135,913,0.99,1\n727,135,915,0.99,1\n728,135,917,0.99,1\n729,136,921,0.99,1\n730,136,925,0.99,1\n731,136,929,0.99,1\n732,136,933,0.99,1\n733,136,937,0.99,1\n734,136,941,0.99,1\n735,137,947,0.99,1\n736,137,953,0.99,1\n737,137,959,0.99,1\n738,137,965,0.99,1\n739,137,971,0.99,1\n740,137,977,0.99,1\n741,137,983,0.99,1\n742,137,989,0.99,1\n743,137,995,0.99,1\n744,138,1004,0.99,1\n745,138,1013,0.99,1\n746,138,1022,0.99,1\n747,138,1031,0.99,1\n748,138,1040,0.99,1\n749,138,1049,0.99,1\n750,138,1058,0.99,1\n751,138,1067,0.99,1\n752,138,1076,0.99,1\n753,138,1085,0.99,1\n754,138,1094,0.99,1\n755,138,1103,0.99,1\n756,138,1112,0.99,1\n757,138,1121,0.99,1\n758,139,1135,0.99,1\n759,140,1136,0.99,1\n760,140,1137,0.99,1\n761,141,1139,0.99,1\n762,141,1141,0.99,1\n763,142,1143,0.99,1\n764,142,1145,0.99,1\n765,142,1147,0.99,1\n766,142,1149,0.99,1\n767,143,1153,0.99,1\n768,143,1157,0.99,1\n769,143,1161,0.99,1\n770,143,1165,0.99,1\n771,143,1169,0.99,1\n772,143,1173,0.99,1\n773,144,1179,0.99,1\n774,144,1185,0.99,1\n775,144,1191,0.99,1\n776,144,1197,0.99,1\n777,144,1203,0.99,1\n778,144,1209,0.99,1\n779,144,1215,0.99,1\n780,144,1221,0.99,1\n781,144,1227,0.99,1\n782,145,1236,0.99,1\n783,145,1245,0.99,1\n784,145,1254,0.99,1\n785,145,1263,0.99,1\n786,145,1272,0.99,1\n787,145,1281,0.99,1\n788,145,1290,0.99,1\n789,145,1299,0.99,1\n790,145,1308,0.99,1\n791,145,1317,0.99,1\n792,145,1326,0.99,1\n793,145,1335,0.99,1\n794,145,1344,0.99,1\n795,145,1353,0.99,1\n796,146,1367,0.99,1\n797,147,1368,0.99,1\n798,147,1369,0.99,1\n799,148,1371,0.99,1\n800,148,1373,0.99,1\n801,149,1375,0.99,1\n802,149,1377,0.99,1\n803,149,1379,0.99,1\n804,149,1381,0.99,1\n805,150,1385,0.99,1\n806,150,1389,0.99,1\n807,150,1393,0.99,1\n808,150,1397,0.99,1\n809,150,1401,0.99,1\n810,150,1405,0.99,1\n811,151,1411,0.99,1\n812,151,1417,0.99,1\n813,151,1423,0.99,1\n814,151,1429,0.99,1\n815,151,1435,0.99,1\n816,151,1441,0.99,1\n817,151,1447,0.99,1\n818,151,1453,0.99,1\n819,151,1459,0.99,1\n820,152,1468,0.99,1\n821,152,1477,0.99,1\n822,152,1486,0.99,1\n823,152,1495,0.99,1\n824,152,1504,0.99,1\n825,152,1513,0.99,1\n826,152,1522,0.99,1\n827,152,1531,0.99,1\n828,152,1540,0.99,1\n829,152,1549,0.99,1\n830,152,1558,0.99,1\n831,152,1567,0.99,1\n832,152,1576,0.99,1\n833,152,1585,0.99,1\n834,153,1599,0.99,1\n835,154,1600,0.99,1\n836,154,1601,0.99,1\n837,155,1603,0.99,1\n838,155,1605,0.99,1\n839,156,1607,0.99,1\n840,156,1609,0.99,1\n841,156,1611,0.99,1\n842,156,1613,0.99,1\n843,157,1617,0.99,1\n844,157,1621,0.99,1\n845,157,1625,0.99,1\n846,157,1629,0.99,1\n847,157,1633,0.99,1\n848,157,1637,0.99,1\n849,158,1643,0.99,1\n850,158,1649,0.99,1\n851,158,1655,0.99,1\n852,158,1661,0.99,1\n853,158,1667,0.99,1\n854,158,1673,0.99,1\n855,158,1679,0.99,1\n856,158,1685,0.99,1\n857,158,1691,0.99,1\n858,159,1700,0.99,1\n859,159,1709,0.99,1\n860,159,1718,0.99,1\n861,159,1727,0.99,1\n862,159,1736,0.99,1\n863,159,1745,0.99,1\n864,159,1754,0.99,1\n865,159,1763,0.99,1\n866,159,1772,0.99,1\n867,159,1781,0.99,1\n868,159,1790,0.99,1\n869,159,1799,0.99,1\n870,159,1808,0.99,1\n871,159,1817,0.99,1\n872,160,1831,0.99,1\n873,161,1832,0.99,1\n874,161,1833,0.99,1\n875,162,1835,0.99,1\n876,162,1837,0.99,1\n877,163,1839,0.99,1\n878,163,1841,0.99,1\n879,163,1843,0.99,1\n880,163,1845,0.99,1\n881,164,1849,0.99,1\n882,164,1853,0.99,1\n883,164,1857,0.99,1\n884,164,1861,0.99,1\n885,164,1865,0.99,1\n886,164,1869,0.99,1\n887,165,1875,0.99,1\n888,165,1881,0.99,1\n889,165,1887,0.99,1\n890,165,1893,0.99,1\n891,165,1899,0.99,1\n892,165,1905,0.99,1\n893,165,1911,0.99,1\n894,165,1917,0.99,1\n895,165,1923,0.99,1\n896,166,1932,0.99,1\n897,166,1941,0.99,1\n898,166,1950,0.99,1\n899,166,1959,0.99,1\n900,166,1968,0.99,1\n901,166,1977,0.99,1\n902,166,1986,0.99,1\n903,166,1995,0.99,1\n904,166,2004,0.99,1\n905,166,2013,0.99,1\n906,166,2022,0.99,1\n907,166,2031,0.99,1\n908,166,2040,0.99,1\n909,166,2049,0.99,1\n910,167,2063,0.99,1\n911,168,2064,0.99,1\n912,168,2065,0.99,1\n913,169,2067,0.99,1\n914,169,2069,0.99,1\n915,170,2071,0.99,1\n916,170,2073,0.99,1\n917,170,2075,0.99,1\n918,170,2077,0.99,1\n919,171,2081,0.99,1\n920,171,2085,0.99,1\n921,171,2089,0.99,1\n922,171,2093,0.99,1\n923,171,2097,0.99,1\n924,171,2101,0.99,1\n925,172,2107,0.99,1\n926,172,2113,0.99,1\n927,172,2119,0.99,1\n928,172,2125,0.99,1\n929,172,2131,0.99,1\n930,172,2137,0.99,1\n931,172,2143,0.99,1\n932,172,2149,0.99,1\n933,172,2155,0.99,1\n934,173,2164,0.99,1\n935,173,2173,0.99,1\n936,173,2182,0.99,1\n937,173,2191,0.99,1\n938,173,2200,0.99,1\n939,173,2209,0.99,1\n940,173,2218,0.99,1\n941,173,2227,0.99,1\n942,173,2236,0.99,1\n943,173,2245,0.99,1\n944,173,2254,0.99,1\n945,173,2263,0.99,1\n946,173,2272,0.99,1\n947,173,2281,0.99,1\n948,174,2295,0.99,1\n949,175,2296,0.99,1\n950,175,2297,0.99,1\n951,176,2299,0.99,1\n952,176,2301,0.99,1\n953,177,2303,0.99,1\n954,177,2305,0.99,1\n955,177,2307,0.99,1\n956,177,2309,0.99,1\n957,178,2313,0.99,1\n958,178,2317,0.99,1\n959,178,2321,0.99,1\n960,178,2325,0.99,1\n961,178,2329,0.99,1\n962,178,2333,0.99,1\n963,179,2339,0.99,1\n964,179,2345,0.99,1\n965,179,2351,0.99,1\n966,179,2357,0.99,1\n967,179,2363,0.99,1\n968,179,2369,0.99,1\n969,179,2375,0.99,1\n970,179,2381,0.99,1\n971,179,2387,0.99,1\n972,180,2396,0.99,1\n973,180,2405,0.99,1\n974,180,2414,0.99,1\n975,180,2423,0.99,1\n976,180,2432,0.99,1\n977,180,2441,0.99,1\n978,180,2450,0.99,1\n979,180,2459,0.99,1\n980,180,2468,0.99,1\n981,180,2477,0.99,1\n982,180,2486,0.99,1\n983,180,2495,0.99,1\n984,180,2504,0.99,1\n985,180,2513,0.99,1\n986,181,2527,0.99,1\n987,182,2528,0.99,1\n988,182,2529,0.99,1\n989,183,2531,0.99,1\n990,183,2533,0.99,1\n991,184,2535,0.99,1\n992,184,2537,0.99,1\n993,184,2539,0.99,1\n994,184,2541,0.99,1\n995,185,2545,0.99,1\n996,185,2549,0.99,1\n997,185,2553,0.99,1\n998,185,2557,0.99,1\n999,185,2561,0.99,1\n1000,185,2565,0.99,1\n1001,186,2571,0.99,1\n1002,186,2577,0.99,1\n1003,186,2583,0.99,1\n1004,186,2589,0.99,1\n1005,186,2595,0.99,1\n1006,186,2601,0.99,1\n1007,186,2607,0.99,1\n1008,186,2613,0.99,1\n1009,186,2619,0.99,1\n1010,187,2628,0.99,1\n1011,187,2637,0.99,1\n1012,187,2646,0.99,1\n1013,187,2655,0.99,1\n1014,187,2664,0.99,1\n1015,187,2673,0.99,1\n1016,187,2682,0.99,1\n1017,187,2691,0.99,1\n1018,187,2700,0.99,1\n1019,187,2709,0.99,1\n1020,187,2718,0.99,1\n1021,187,2727,0.99,1\n1022,187,2736,0.99,1\n1023,187,2745,0.99,1\n1024,188,2759,0.99,1\n1025,189,2760,0.99,1\n1026,189,2761,0.99,1\n1027,190,2763,0.99,1\n1028,190,2765,0.99,1\n1029,191,2767,0.99,1\n1030,191,2769,0.99,1\n1031,191,2771,0.99,1\n1032,191,2773,0.99,1\n1033,192,2777,0.99,1\n1034,192,2781,0.99,1\n1035,192,2785,0.99,1\n1036,192,2789,0.99,1\n1037,192,2793,0.99,1\n1038,192,2797,0.99,1\n1039,193,2803,0.99,1\n1040,193,2809,0.99,1\n1041,193,2815,0.99,1\n1042,193,2821,1.99,1\n1043,193,2827,1.99,1\n1044,193,2833,1.99,1\n1045,193,2839,1.99,1\n1046,193,2845,1.99,1\n1047,193,2851,1.99,1\n1048,194,2860,1.99,1\n1049,194,2869,1.99,1\n1050,194,2878,1.99,1\n1051,194,2887,1.99,1\n1052,194,2896,1.99,1\n1053,194,2905,1.99,1\n1054,194,2914,1.99,1\n1055,194,2923,1.99,1\n1056,194,2932,0.99,1\n1057,194,2941,0.99,1\n1058,194,2950,0.99,1\n1059,194,2959,0.99,1\n1060,194,2968,0.99,1\n1061,194,2977,0.99,1\n1062,195,2991,0.99,1\n1063,196,2992,0.99,1\n1064,196,2993,0.99,1\n1065,197,2995,0.99,1\n1066,197,2997,0.99,1\n1067,198,2999,0.99,1\n1068,198,3001,0.99,1\n1069,198,3003,0.99,1\n1070,198,3005,0.99,1\n1071,199,3009,0.99,1\n1072,199,3013,0.99,1\n1073,199,3017,0.99,1\n1074,199,3021,0.99,1\n1075,199,3025,0.99,1\n1076,199,3029,0.99,1\n1077,200,3035,0.99,1\n1078,200,3041,0.99,1\n1079,200,3047,0.99,1\n1080,200,3053,0.99,1\n1081,200,3059,0.99,1\n1082,200,3065,0.99,1\n1083,200,3071,0.99,1\n1084,200,3077,0.99,1\n1085,200,3083,0.99,1\n1086,201,3092,0.99,1\n1087,201,3101,0.99,1\n1088,201,3110,0.99,1\n1089,201,3119,0.99,1\n1090,201,3128,0.99,1\n1091,201,3137,0.99,1\n1092,201,3146,0.99,1\n1093,201,3155,0.99,1\n1094,201,3164,0.99,1\n1095,201,3173,1.99,1\n1096,201,3182,1.99,1\n1097,201,3191,1.99,1\n1098,201,3200,1.99,1\n1099,201,3209,1.99,1\n1100,202,3223,1.99,1\n1101,203,3224,1.99,1\n1102,203,3225,0.99,1\n1103,204,3227,1.99,1\n1104,204,3229,1.99,1\n1105,205,3231,1.99,1\n1106,205,3233,1.99,1\n1107,205,3235,1.99,1\n1108,205,3237,1.99,1\n1109,206,3241,1.99,1\n1110,206,3245,1.99,1\n1111,206,3249,1.99,1\n1112,206,3253,0.99,1\n1113,206,3257,0.99,1\n1114,206,3261,0.99,1\n1115,207,3267,0.99,1\n1116,207,3273,0.99,1\n1117,207,3279,0.99,1\n1118,207,3285,0.99,1\n1119,207,3291,0.99,1\n1120,207,3297,0.99,1\n1121,207,3303,0.99,1\n1122,207,3309,0.99,1\n1123,207,3315,0.99,1\n1124,208,3324,0.99,1\n1125,208,3333,0.99,1\n1126,208,3342,1.99,1\n1127,208,3351,0.99,1\n1128,208,3360,1.99,1\n1129,208,3369,0.99,1\n1130,208,3378,0.99,1\n1131,208,3387,0.99,1\n1132,208,3396,0.99,1\n1133,208,3405,0.99,1\n1134,208,3414,0.99,1\n1135,208,3423,0.99,1\n1136,208,3432,0.99,1\n1137,208,3441,0.99,1\n1138,209,3455,0.99,1\n1139,210,3456,0.99,1\n1140,210,3457,0.99,1\n1141,211,3459,0.99,1\n1142,211,3461,0.99,1\n1143,212,3463,0.99,1\n1144,212,3465,0.99,1\n1145,212,3467,0.99,1\n1146,212,3469,0.99,1\n1147,213,3473,0.99,1\n1148,213,3477,0.99,1\n1149,213,3481,0.99,1\n1150,213,3485,0.99,1\n1151,213,3489,0.99,1\n1152,213,3493,0.99,1\n1153,214,3499,0.99,1\n1154,214,2,0.99,1\n1155,214,8,0.99,1\n1156,214,14,0.99,1\n1157,214,20,0.99,1\n1158,214,26,0.99,1\n1159,214,32,0.99,1\n1160,214,38,0.99,1\n1161,214,44,0.99,1\n1162,215,53,0.99,1\n1163,215,62,0.99,1\n1164,215,71,0.99,1\n1165,215,80,0.99,1\n1166,215,89,0.99,1\n1167,215,98,0.99,1\n1168,215,107,0.99,1\n1169,215,116,0.99,1\n1170,215,125,0.99,1\n1171,215,134,0.99,1\n1172,215,143,0.99,1\n1173,215,152,0.99,1\n1174,215,161,0.99,1\n1175,215,170,0.99,1\n1176,216,184,0.99,1\n1177,217,185,0.99,1\n1178,217,186,0.99,1\n1179,218,188,0.99,1\n1180,218,190,0.99,1\n1181,219,192,0.99,1\n1182,219,194,0.99,1\n1183,219,196,0.99,1\n1184,219,198,0.99,1\n1185,220,202,0.99,1\n1186,220,206,0.99,1\n1187,220,210,0.99,1\n1188,220,214,0.99,1\n1189,220,218,0.99,1\n1190,220,222,0.99,1\n1191,221,228,0.99,1\n1192,221,234,0.99,1\n1193,221,240,0.99,1\n1194,221,246,0.99,1\n1195,221,252,0.99,1\n1196,221,258,0.99,1\n1197,221,264,0.99,1\n1198,221,270,0.99,1\n1199,221,276,0.99,1\n1200,222,285,0.99,1\n1201,222,294,0.99,1\n1202,222,303,0.99,1\n1203,222,312,0.99,1\n1204,222,321,0.99,1\n1205,222,330,0.99,1\n1206,222,339,0.99,1\n1207,222,348,0.99,1\n1208,222,357,0.99,1\n1209,222,366,0.99,1\n1210,222,375,0.99,1\n1211,222,384,0.99,1\n1212,222,393,0.99,1\n1213,222,402,0.99,1\n1214,223,416,0.99,1\n1215,224,417,0.99,1\n1216,224,418,0.99,1\n1217,225,420,0.99,1\n1218,225,422,0.99,1\n1219,226,424,0.99,1\n1220,226,426,0.99,1\n1221,226,428,0.99,1\n1222,226,430,0.99,1\n1223,227,434,0.99,1\n1224,227,438,0.99,1\n1225,227,442,0.99,1\n1226,227,446,0.99,1\n1227,227,450,0.99,1\n1228,227,454,0.99,1\n1229,228,460,0.99,1\n1230,228,466,0.99,1\n1231,228,472,0.99,1\n1232,228,478,0.99,1\n1233,228,484,0.99,1\n1234,228,490,0.99,1\n1235,228,496,0.99,1\n1236,228,502,0.99,1\n1237,228,508,0.99,1\n1238,229,517,0.99,1\n1239,229,526,0.99,1\n1240,229,535,0.99,1\n1241,229,544,0.99,1\n1242,229,553,0.99,1\n1243,229,562,0.99,1\n1244,229,571,0.99,1\n1245,229,580,0.99,1\n1246,229,589,0.99,1\n1247,229,598,0.99,1\n1248,229,607,0.99,1\n1249,229,616,0.99,1\n1250,229,625,0.99,1\n1251,229,634,0.99,1\n1252,230,648,0.99,1\n1253,231,649,0.99,1\n1254,231,650,0.99,1\n1255,232,652,0.99,1\n1256,232,654,0.99,1\n1257,233,656,0.99,1\n1258,233,658,0.99,1\n1259,233,660,0.99,1\n1260,233,662,0.99,1\n1261,234,666,0.99,1\n1262,234,670,0.99,1\n1263,234,674,0.99,1\n1264,234,678,0.99,1\n1265,234,682,0.99,1\n1266,234,686,0.99,1\n1267,235,692,0.99,1\n1268,235,698,0.99,1\n1269,235,704,0.99,1\n1270,235,710,0.99,1\n1271,235,716,0.99,1\n1272,235,722,0.99,1\n1273,235,728,0.99,1\n1274,235,734,0.99,1\n1275,235,740,0.99,1\n1276,236,749,0.99,1\n1277,236,758,0.99,1\n1278,236,767,0.99,1\n1279,236,776,0.99,1\n1280,236,785,0.99,1\n1281,236,794,0.99,1\n1282,236,803,0.99,1\n1283,236,812,0.99,1\n1284,236,821,0.99,1\n1285,236,830,0.99,1\n1286,236,839,0.99,1\n1287,236,848,0.99,1\n1288,236,857,0.99,1\n1289,236,866,0.99,1\n1290,237,880,0.99,1\n1291,238,881,0.99,1\n1292,238,882,0.99,1\n1293,239,884,0.99,1\n1294,239,886,0.99,1\n1295,240,888,0.99,1\n1296,240,890,0.99,1\n1297,240,892,0.99,1\n1298,240,894,0.99,1\n1299,241,898,0.99,1\n1300,241,902,0.99,1\n1301,241,906,0.99,1\n1302,241,910,0.99,1\n1303,241,914,0.99,1\n1304,241,918,0.99,1\n1305,242,924,0.99,1\n1306,242,930,0.99,1\n1307,242,936,0.99,1\n1308,242,942,0.99,1\n1309,242,948,0.99,1\n1310,242,954,0.99,1\n1311,242,960,0.99,1\n1312,242,966,0.99,1\n1313,242,972,0.99,1\n1314,243,981,0.99,1\n1315,243,990,0.99,1\n1316,243,999,0.99,1\n1317,243,1008,0.99,1\n1318,243,1017,0.99,1\n1319,243,1026,0.99,1\n1320,243,1035,0.99,1\n1321,243,1044,0.99,1\n1322,243,1053,0.99,1\n1323,243,1062,0.99,1\n1324,243,1071,0.99,1\n1325,243,1080,0.99,1\n1326,243,1089,0.99,1\n1327,243,1098,0.99,1\n1328,244,1112,0.99,1\n1329,245,1113,0.99,1\n1330,245,1114,0.99,1\n1331,246,1116,0.99,1\n1332,246,1118,0.99,1\n1333,247,1120,0.99,1\n1334,247,1122,0.99,1\n1335,247,1124,0.99,1\n1336,247,1126,0.99,1\n1337,248,1130,0.99,1\n1338,248,1134,0.99,1\n1339,248,1138,0.99,1\n1340,248,1142,0.99,1\n1341,248,1146,0.99,1\n1342,248,1150,0.99,1\n1343,249,1156,0.99,1\n1344,249,1162,0.99,1\n1345,249,1168,0.99,1\n1346,249,1174,0.99,1\n1347,249,1180,0.99,1\n1348,249,1186,0.99,1\n1349,249,1192,0.99,1\n1350,249,1198,0.99,1\n1351,249,1204,0.99,1\n1352,250,1213,0.99,1\n1353,250,1222,0.99,1\n1354,250,1231,0.99,1\n1355,250,1240,0.99,1\n1356,250,1249,0.99,1\n1357,250,1258,0.99,1\n1358,250,1267,0.99,1\n1359,250,1276,0.99,1\n1360,250,1285,0.99,1\n1361,250,1294,0.99,1\n1362,250,1303,0.99,1\n1363,250,1312,0.99,1\n1364,250,1321,0.99,1\n1365,250,1330,0.99,1\n1366,251,1344,0.99,1\n1367,252,1345,0.99,1\n1368,252,1346,0.99,1\n1369,253,1348,0.99,1\n1370,253,1350,0.99,1\n1371,254,1352,0.99,1\n1372,254,1354,0.99,1\n1373,254,1356,0.99,1\n1374,254,1358,0.99,1\n1375,255,1362,0.99,1\n1376,255,1366,0.99,1\n1377,255,1370,0.99,1\n1378,255,1374,0.99,1\n1379,255,1378,0.99,1\n1380,255,1382,0.99,1\n1381,256,1388,0.99,1\n1382,256,1394,0.99,1\n1383,256,1400,0.99,1\n1384,256,1406,0.99,1\n1385,256,1412,0.99,1\n1386,256,1418,0.99,1\n1387,256,1424,0.99,1\n1388,256,1430,0.99,1\n1389,256,1436,0.99,1\n1390,257,1445,0.99,1\n1391,257,1454,0.99,1\n1392,257,1463,0.99,1\n1393,257,1472,0.99,1\n1394,257,1481,0.99,1\n1395,257,1490,0.99,1\n1396,257,1499,0.99,1\n1397,257,1508,0.99,1\n1398,257,1517,0.99,1\n1399,257,1526,0.99,1\n1400,257,1535,0.99,1\n1401,257,1544,0.99,1\n1402,257,1553,0.99,1\n1403,257,1562,0.99,1\n1404,258,1576,0.99,1\n1405,259,1577,0.99,1\n1406,259,1578,0.99,1\n1407,260,1580,0.99,1\n1408,260,1582,0.99,1\n1409,261,1584,0.99,1\n1410,261,1586,0.99,1\n1411,261,1588,0.99,1\n1412,261,1590,0.99,1\n1413,262,1594,0.99,1\n1414,262,1598,0.99,1\n1415,262,1602,0.99,1\n1416,262,1606,0.99,1\n1417,262,1610,0.99,1\n1418,262,1614,0.99,1\n1419,263,1620,0.99,1\n1420,263,1626,0.99,1\n1421,263,1632,0.99,1\n1422,263,1638,0.99,1\n1423,263,1644,0.99,1\n1424,263,1650,0.99,1\n1425,263,1656,0.99,1\n1426,263,1662,0.99,1\n1427,263,1668,0.99,1\n1428,264,1677,0.99,1\n1429,264,1686,0.99,1\n1430,264,1695,0.99,1\n1431,264,1704,0.99,1\n1432,264,1713,0.99,1\n1433,264,1722,0.99,1\n1434,264,1731,0.99,1\n1435,264,1740,0.99,1\n1436,264,1749,0.99,1\n1437,264,1758,0.99,1\n1438,264,1767,0.99,1\n1439,264,1776,0.99,1\n1440,264,1785,0.99,1\n1441,264,1794,0.99,1\n1442,265,1808,0.99,1\n1443,266,1809,0.99,1\n1444,266,1810,0.99,1\n1445,267,1812,0.99,1\n1446,267,1814,0.99,1\n1447,268,1816,0.99,1\n1448,268,1818,0.99,1\n1449,268,1820,0.99,1\n1450,268,1822,0.99,1\n1451,269,1826,0.99,1\n1452,269,1830,0.99,1\n1453,269,1834,0.99,1\n1454,269,1838,0.99,1\n1455,269,1842,0.99,1\n1456,269,1846,0.99,1\n1457,270,1852,0.99,1\n1458,270,1858,0.99,1\n1459,270,1864,0.99,1\n1460,270,1870,0.99,1\n1461,270,1876,0.99,1\n1462,270,1882,0.99,1\n1463,270,1888,0.99,1\n1464,270,1894,0.99,1\n1465,270,1900,0.99,1\n1466,271,1909,0.99,1\n1467,271,1918,0.99,1\n1468,271,1927,0.99,1\n1469,271,1936,0.99,1\n1470,271,1945,0.99,1\n1471,271,1954,0.99,1\n1472,271,1963,0.99,1\n1473,271,1972,0.99,1\n1474,271,1981,0.99,1\n1475,271,1990,0.99,1\n1476,271,1999,0.99,1\n1477,271,2008,0.99,1\n1478,271,2017,0.99,1\n1479,271,2026,0.99,1\n1480,272,2040,0.99,1\n1481,273,2041,0.99,1\n1482,273,2042,0.99,1\n1483,274,2044,0.99,1\n1484,274,2046,0.99,1\n1485,275,2048,0.99,1\n1486,275,2050,0.99,1\n1487,275,2052,0.99,1\n1488,275,2054,0.99,1\n1489,276,2058,0.99,1\n1490,276,2062,0.99,1\n1491,276,2066,0.99,1\n1492,276,2070,0.99,1\n1493,276,2074,0.99,1\n1494,276,2078,0.99,1\n1495,277,2084,0.99,1\n1496,277,2090,0.99,1\n1497,277,2096,0.99,1\n1498,277,2102,0.99,1\n1499,277,2108,0.99,1\n1500,277,2114,0.99,1\n1501,277,2120,0.99,1\n1502,277,2126,0.99,1\n1503,277,2132,0.99,1\n1504,278,2141,0.99,1\n1505,278,2150,0.99,1\n1506,278,2159,0.99,1\n1507,278,2168,0.99,1\n1508,278,2177,0.99,1\n1509,278,2186,0.99,1\n1510,278,2195,0.99,1\n1511,278,2204,0.99,1\n1512,278,2213,0.99,1\n1513,278,2222,0.99,1\n1514,278,2231,0.99,1\n1515,278,2240,0.99,1\n1516,278,2249,0.99,1\n1517,278,2258,0.99,1\n1518,279,2272,0.99,1\n1519,280,2273,0.99,1\n1520,280,2274,0.99,1\n1521,281,2276,0.99,1\n1522,281,2278,0.99,1\n1523,282,2280,0.99,1\n1524,282,2282,0.99,1\n1525,282,2284,0.99,1\n1526,282,2286,0.99,1\n1527,283,2290,0.99,1\n1528,283,2294,0.99,1\n1529,283,2298,0.99,1\n1530,283,2302,0.99,1\n1531,283,2306,0.99,1\n1532,283,2310,0.99,1\n1533,284,2316,0.99,1\n1534,284,2322,0.99,1\n1535,284,2328,0.99,1\n1536,284,2334,0.99,1\n1537,284,2340,0.99,1\n1538,284,2346,0.99,1\n1539,284,2352,0.99,1\n1540,284,2358,0.99,1\n1541,284,2364,0.99,1\n1542,285,2373,0.99,1\n1543,285,2382,0.99,1\n1544,285,2391,0.99,1\n1545,285,2400,0.99,1\n1546,285,2409,0.99,1\n1547,285,2418,0.99,1\n1548,285,2427,0.99,1\n1549,285,2436,0.99,1\n1550,285,2445,0.99,1\n1551,285,2454,0.99,1\n1552,285,2463,0.99,1\n1553,285,2472,0.99,1\n1554,285,2481,0.99,1\n1555,285,2490,0.99,1\n1556,286,2504,0.99,1\n1557,287,2505,0.99,1\n1558,287,2506,0.99,1\n1559,288,2508,0.99,1\n1560,288,2510,0.99,1\n1561,289,2512,0.99,1\n1562,289,2514,0.99,1\n1563,289,2516,0.99,1\n1564,289,2518,0.99,1\n1565,290,2522,0.99,1\n1566,290,2526,0.99,1\n1567,290,2530,0.99,1\n1568,290,2534,0.99,1\n1569,290,2538,0.99,1\n1570,290,2542,0.99,1\n1571,291,2548,0.99,1\n1572,291,2554,0.99,1\n1573,291,2560,0.99,1\n1574,291,2566,0.99,1\n1575,291,2572,0.99,1\n1576,291,2578,0.99,1\n1577,291,2584,0.99,1\n1578,291,2590,0.99,1\n1579,291,2596,0.99,1\n1580,292,2605,0.99,1\n1581,292,2614,0.99,1\n1582,292,2623,0.99,1\n1583,292,2632,0.99,1\n1584,292,2641,0.99,1\n1585,292,2650,0.99,1\n1586,292,2659,0.99,1\n1587,292,2668,0.99,1\n1588,292,2677,0.99,1\n1589,292,2686,0.99,1\n1590,292,2695,0.99,1\n1591,292,2704,0.99,1\n1592,292,2713,0.99,1\n1593,292,2722,0.99,1\n1594,293,2736,0.99,1\n1595,294,2737,0.99,1\n1596,294,2738,0.99,1\n1597,295,2740,0.99,1\n1598,295,2742,0.99,1\n1599,296,2744,0.99,1\n1600,296,2746,0.99,1\n1601,296,2748,0.99,1\n1602,296,2750,0.99,1\n1603,297,2754,0.99,1\n1604,297,2758,0.99,1\n1605,297,2762,0.99,1\n1606,297,2766,0.99,1\n1607,297,2770,0.99,1\n1608,297,2774,0.99,1\n1609,298,2780,0.99,1\n1610,298,2786,0.99,1\n1611,298,2792,0.99,1\n1612,298,2798,0.99,1\n1613,298,2804,0.99,1\n1614,298,2810,0.99,1\n1615,298,2816,0.99,1\n1616,298,2822,1.99,1\n1617,298,2828,1.99,1\n1618,299,2837,1.99,1\n1619,299,2846,1.99,1\n1620,299,2855,1.99,1\n1621,299,2864,1.99,1\n1622,299,2873,1.99,1\n1623,299,2882,1.99,1\n1624,299,2891,1.99,1\n1625,299,2900,1.99,1\n1626,299,2909,1.99,1\n1627,299,2918,1.99,1\n1628,299,2927,0.99,1\n1629,299,2936,0.99,1\n1630,299,2945,0.99,1\n1631,299,2954,0.99,1\n1632,300,2968,0.99,1\n1633,301,2969,0.99,1\n1634,301,2970,0.99,1\n1635,302,2972,0.99,1\n1636,302,2974,0.99,1\n1637,303,2976,0.99,1\n1638,303,2978,0.99,1\n1639,303,2980,0.99,1\n1640,303,2982,0.99,1\n1641,304,2986,0.99,1\n1642,304,2990,0.99,1\n1643,304,2994,0.99,1\n1644,304,2998,0.99,1\n1645,304,3002,0.99,1\n1646,304,3006,0.99,1\n1647,305,3012,0.99,1\n1648,305,3018,0.99,1\n1649,305,3024,0.99,1\n1650,305,3030,0.99,1\n1651,305,3036,0.99,1\n1652,305,3042,0.99,1\n1653,305,3048,0.99,1\n1654,305,3054,0.99,1\n1655,305,3060,0.99,1\n1656,306,3069,0.99,1\n1657,306,3078,0.99,1\n1658,306,3087,0.99,1\n1659,306,3096,0.99,1\n1660,306,3105,0.99,1\n1661,306,3114,0.99,1\n1662,306,3123,0.99,1\n1663,306,3132,0.99,1\n1664,306,3141,0.99,1\n1665,306,3150,0.99,1\n1666,306,3159,0.99,1\n1667,306,3168,1.99,1\n1668,306,3177,1.99,1\n1669,306,3186,1.99,1\n1670,307,3200,1.99,1\n1671,308,3201,1.99,1\n1672,308,3202,1.99,1\n1673,309,3204,1.99,1\n1674,309,3206,1.99,1\n1675,310,3208,1.99,1\n1676,310,3210,1.99,1\n1677,310,3212,1.99,1\n1678,310,3214,1.99,1\n1679,311,3218,1.99,1\n1680,311,3222,1.99,1\n1681,311,3226,1.99,1\n1682,311,3230,1.99,1\n1683,311,3234,1.99,1\n1684,311,3238,1.99,1\n1685,312,3244,1.99,1\n1686,312,3250,1.99,1\n1687,312,3256,0.99,1\n1688,312,3262,0.99,1\n1689,312,3268,0.99,1\n1690,312,3274,0.99,1\n1691,312,3280,0.99,1\n1692,312,3286,0.99,1\n1693,312,3292,0.99,1\n1694,313,3301,0.99,1\n1695,313,3310,0.99,1\n1696,313,3319,0.99,1\n1697,313,3328,0.99,1\n1698,313,3337,1.99,1\n1699,313,3346,1.99,1\n1700,313,3355,0.99,1\n1701,313,3364,1.99,1\n1702,313,3373,0.99,1\n1703,313,3382,0.99,1\n1704,313,3391,0.99,1\n1705,313,3400,0.99,1\n1706,313,3409,0.99,1\n1707,313,3418,0.99,1\n1708,314,3432,0.99,1\n1709,315,3433,0.99,1\n1710,315,3434,0.99,1\n1711,316,3436,0.99,1\n1712,316,3438,0.99,1\n1713,317,3440,0.99,1\n1714,317,3442,0.99,1\n1715,317,3444,0.99,1\n1716,317,3446,0.99,1\n1717,318,3450,0.99,1\n1718,318,3454,0.99,1\n1719,318,3458,0.99,1\n1720,318,3462,0.99,1\n1721,318,3466,0.99,1\n1722,318,3470,0.99,1\n1723,319,3476,0.99,1\n1724,319,3482,0.99,1\n1725,319,3488,0.99,1\n1726,319,3494,0.99,1\n1727,319,3500,0.99,1\n1728,319,3,0.99,1\n1729,319,9,0.99,1\n1730,319,15,0.99,1\n1731,319,21,0.99,1\n1732,320,30,0.99,1\n1733,320,39,0.99,1\n1734,320,48,0.99,1\n1735,320,57,0.99,1\n1736,320,66,0.99,1\n1737,320,75,0.99,1\n1738,320,84,0.99,1\n1739,320,93,0.99,1\n1740,320,102,0.99,1\n1741,320,111,0.99,1\n1742,320,120,0.99,1\n1743,320,129,0.99,1\n1744,320,138,0.99,1\n1745,320,147,0.99,1\n1746,321,161,0.99,1\n1747,322,162,0.99,1\n1748,322,163,0.99,1\n1749,323,165,0.99,1\n1750,323,167,0.99,1\n1751,324,169,0.99,1\n1752,324,171,0.99,1\n1753,324,173,0.99,1\n1754,324,175,0.99,1\n1755,325,179,0.99,1\n1756,325,183,0.99,1\n1757,325,187,0.99,1\n1758,325,191,0.99,1\n1759,325,195,0.99,1\n1760,325,199,0.99,1\n1761,326,205,0.99,1\n1762,326,211,0.99,1\n1763,326,217,0.99,1\n1764,326,223,0.99,1\n1765,326,229,0.99,1\n1766,326,235,0.99,1\n1767,326,241,0.99,1\n1768,326,247,0.99,1\n1769,326,253,0.99,1\n1770,327,262,0.99,1\n1771,327,271,0.99,1\n1772,327,280,0.99,1\n1773,327,289,0.99,1\n1774,327,298,0.99,1\n1775,327,307,0.99,1\n1776,327,316,0.99,1\n1777,327,325,0.99,1\n1778,327,334,0.99,1\n1779,327,343,0.99,1\n1780,327,352,0.99,1\n1781,327,361,0.99,1\n1782,327,370,0.99,1\n1783,327,379,0.99,1\n1784,328,393,0.99,1\n1785,329,394,0.99,1\n1786,329,395,0.99,1\n1787,330,397,0.99,1\n1788,330,399,0.99,1\n1789,331,401,0.99,1\n1790,331,403,0.99,1\n1791,331,405,0.99,1\n1792,331,407,0.99,1\n1793,332,411,0.99,1\n1794,332,415,0.99,1\n1795,332,419,0.99,1\n1796,332,423,0.99,1\n1797,332,427,0.99,1\n1798,332,431,0.99,1\n1799,333,437,0.99,1\n1800,333,443,0.99,1\n1801,333,449,0.99,1\n1802,333,455,0.99,1\n1803,333,461,0.99,1\n1804,333,467,0.99,1\n1805,333,473,0.99,1\n1806,333,479,0.99,1\n1807,333,485,0.99,1\n1808,334,494,0.99,1\n1809,334,503,0.99,1\n1810,334,512,0.99,1\n1811,334,521,0.99,1\n1812,334,530,0.99,1\n1813,334,539,0.99,1\n1814,334,548,0.99,1\n1815,334,557,0.99,1\n1816,334,566,0.99,1\n1817,334,575,0.99,1\n1818,334,584,0.99,1\n1819,334,593,0.99,1\n1820,334,602,0.99,1\n1821,334,611,0.99,1\n1822,335,625,0.99,1\n1823,336,626,0.99,1\n1824,336,627,0.99,1\n1825,337,629,0.99,1\n1826,337,631,0.99,1\n1827,338,633,0.99,1\n1828,338,635,0.99,1\n1829,338,637,0.99,1\n1830,338,639,0.99,1\n1831,339,643,0.99,1\n1832,339,647,0.99,1\n1833,339,651,0.99,1\n1834,339,655,0.99,1\n1835,339,659,0.99,1\n1836,339,663,0.99,1\n1837,340,669,0.99,1\n1838,340,675,0.99,1\n1839,340,681,0.99,1\n1840,340,687,0.99,1\n1841,340,693,0.99,1\n1842,340,699,0.99,1\n1843,340,705,0.99,1\n1844,340,711,0.99,1\n1845,340,717,0.99,1\n1846,341,726,0.99,1\n1847,341,735,0.99,1\n1848,341,744,0.99,1\n1849,341,753,0.99,1\n1850,341,762,0.99,1\n1851,341,771,0.99,1\n1852,341,780,0.99,1\n1853,341,789,0.99,1\n1854,341,798,0.99,1\n1855,341,807,0.99,1\n1856,341,816,0.99,1\n1857,341,825,0.99,1\n1858,341,834,0.99,1\n1859,341,843,0.99,1\n1860,342,857,0.99,1\n1861,343,858,0.99,1\n1862,343,859,0.99,1\n1863,344,861,0.99,1\n1864,344,863,0.99,1\n1865,345,865,0.99,1\n1866,345,867,0.99,1\n1867,345,869,0.99,1\n1868,345,871,0.99,1\n1869,346,875,0.99,1\n1870,346,879,0.99,1\n1871,346,883,0.99,1\n1872,346,887,0.99,1\n1873,346,891,0.99,1\n1874,346,895,0.99,1\n1875,347,901,0.99,1\n1876,347,907,0.99,1\n1877,347,913,0.99,1\n1878,347,919,0.99,1\n1879,347,925,0.99,1\n1880,347,931,0.99,1\n1881,347,937,0.99,1\n1882,347,943,0.99,1\n1883,347,949,0.99,1\n1884,348,958,0.99,1\n1885,348,967,0.99,1\n1886,348,976,0.99,1\n1887,348,985,0.99,1\n1888,348,994,0.99,1\n1889,348,1003,0.99,1\n1890,348,1012,0.99,1\n1891,348,1021,0.99,1\n1892,348,1030,0.99,1\n1893,348,1039,0.99,1\n1894,348,1048,0.99,1\n1895,348,1057,0.99,1\n1896,348,1066,0.99,1\n1897,348,1075,0.99,1\n1898,349,1089,0.99,1\n1899,350,1090,0.99,1\n1900,350,1091,0.99,1\n1901,351,1093,0.99,1\n1902,351,1095,0.99,1\n1903,352,1097,0.99,1\n1904,352,1099,0.99,1\n1905,352,1101,0.99,1\n1906,352,1103,0.99,1\n1907,353,1107,0.99,1\n1908,353,1111,0.99,1\n1909,353,1115,0.99,1\n1910,353,1119,0.99,1\n1911,353,1123,0.99,1\n1912,353,1127,0.99,1\n1913,354,1133,0.99,1\n1914,354,1139,0.99,1\n1915,354,1145,0.99,1\n1916,354,1151,0.99,1\n1917,354,1157,0.99,1\n1918,354,1163,0.99,1\n1919,354,1169,0.99,1\n1920,354,1175,0.99,1\n1921,354,1181,0.99,1\n1922,355,1190,0.99,1\n1923,355,1199,0.99,1\n1924,355,1208,0.99,1\n1925,355,1217,0.99,1\n1926,355,1226,0.99,1\n1927,355,1235,0.99,1\n1928,355,1244,0.99,1\n1929,355,1253,0.99,1\n1930,355,1262,0.99,1\n1931,355,1271,0.99,1\n1932,355,1280,0.99,1\n1933,355,1289,0.99,1\n1934,355,1298,0.99,1\n1935,355,1307,0.99,1\n1936,356,1321,0.99,1\n1937,357,1322,0.99,1\n1938,357,1323,0.99,1\n1939,358,1325,0.99,1\n1940,358,1327,0.99,1\n1941,359,1329,0.99,1\n1942,359,1331,0.99,1\n1943,359,1333,0.99,1\n1944,359,1335,0.99,1\n1945,360,1339,0.99,1\n1946,360,1343,0.99,1\n1947,360,1347,0.99,1\n1948,360,1351,0.99,1\n1949,360,1355,0.99,1\n1950,360,1359,0.99,1\n1951,361,1365,0.99,1\n1952,361,1371,0.99,1\n1953,361,1377,0.99,1\n1954,361,1383,0.99,1\n1955,361,1389,0.99,1\n1956,361,1395,0.99,1\n1957,361,1401,0.99,1\n1958,361,1407,0.99,1\n1959,361,1413,0.99,1\n1960,362,1422,0.99,1\n1961,362,1431,0.99,1\n1962,362,1440,0.99,1\n1963,362,1449,0.99,1\n1964,362,1458,0.99,1\n1965,362,1467,0.99,1\n1966,362,1476,0.99,1\n1967,362,1485,0.99,1\n1968,362,1494,0.99,1\n1969,362,1503,0.99,1\n1970,362,1512,0.99,1\n1971,362,1521,0.99,1\n1972,362,1530,0.99,1\n1973,362,1539,0.99,1\n1974,363,1553,0.99,1\n1975,364,1554,0.99,1\n1976,364,1555,0.99,1\n1977,365,1557,0.99,1\n1978,365,1559,0.99,1\n1979,366,1561,0.99,1\n1980,366,1563,0.99,1\n1981,366,1565,0.99,1\n1982,366,1567,0.99,1\n1983,367,1571,0.99,1\n1984,367,1575,0.99,1\n1985,367,1579,0.99,1\n1986,367,1583,0.99,1\n1987,367,1587,0.99,1\n1988,367,1591,0.99,1\n1989,368,1597,0.99,1\n1990,368,1603,0.99,1\n1991,368,1609,0.99,1\n1992,368,1615,0.99,1\n1993,368,1621,0.99,1\n1994,368,1627,0.99,1\n1995,368,1633,0.99,1\n1996,368,1639,0.99,1\n1997,368,1645,0.99,1\n1998,369,1654,0.99,1\n1999,369,1663,0.99,1\n2000,369,1672,0.99,1\n2001,369,1681,0.99,1\n2002,369,1690,0.99,1\n2003,369,1699,0.99,1\n2004,369,1708,0.99,1\n2005,369,1717,0.99,1\n2006,369,1726,0.99,1\n2007,369,1735,0.99,1\n2008,369,1744,0.99,1\n2009,369,1753,0.99,1\n2010,369,1762,0.99,1\n2011,369,1771,0.99,1\n2012,370,1785,0.99,1\n2013,371,1786,0.99,1\n2014,371,1787,0.99,1\n2015,372,1789,0.99,1\n2016,372,1791,0.99,1\n2017,373,1793,0.99,1\n2018,373,1795,0.99,1\n2019,373,1797,0.99,1\n2020,373,1799,0.99,1\n2021,374,1803,0.99,1\n2022,374,1807,0.99,1\n2023,374,1811,0.99,1\n2024,374,1815,0.99,1\n2025,374,1819,0.99,1\n2026,374,1823,0.99,1\n2027,375,1829,0.99,1\n2028,375,1835,0.99,1\n2029,375,1841,0.99,1\n2030,375,1847,0.99,1\n2031,375,1853,0.99,1\n2032,375,1859,0.99,1\n2033,375,1865,0.99,1\n2034,375,1871,0.99,1\n2035,375,1877,0.99,1\n2036,376,1886,0.99,1\n2037,376,1895,0.99,1\n2038,376,1904,0.99,1\n2039,376,1913,0.99,1\n2040,376,1922,0.99,1\n2041,376,1931,0.99,1\n2042,376,1940,0.99,1\n2043,376,1949,0.99,1\n2044,376,1958,0.99,1\n2045,376,1967,0.99,1\n2046,376,1976,0.99,1\n2047,376,1985,0.99,1\n2048,376,1994,0.99,1\n2049,376,2003,0.99,1\n2050,377,2017,0.99,1\n2051,378,2018,0.99,1\n2052,378,2019,0.99,1\n2053,379,2021,0.99,1\n2054,379,2023,0.99,1\n2055,380,2025,0.99,1\n2056,380,2027,0.99,1\n2057,380,2029,0.99,1\n2058,380,2031,0.99,1\n2059,381,2035,0.99,1\n2060,381,2039,0.99,1\n2061,381,2043,0.99,1\n2062,381,2047,0.99,1\n2063,381,2051,0.99,1\n2064,381,2055,0.99,1\n2065,382,2061,0.99,1\n2066,382,2067,0.99,1\n2067,382,2073,0.99,1\n2068,382,2079,0.99,1\n2069,382,2085,0.99,1\n2070,382,2091,0.99,1\n2071,382,2097,0.99,1\n2072,382,2103,0.99,1\n2073,382,2109,0.99,1\n2074,383,2118,0.99,1\n2075,383,2127,0.99,1\n2076,383,2136,0.99,1\n2077,383,2145,0.99,1\n2078,383,2154,0.99,1\n2079,383,2163,0.99,1\n2080,383,2172,0.99,1\n2081,383,2181,0.99,1\n2082,383,2190,0.99,1\n2083,383,2199,0.99,1\n2084,383,2208,0.99,1\n2085,383,2217,0.99,1\n2086,383,2226,0.99,1\n2087,383,2235,0.99,1\n2088,384,2249,0.99,1\n2089,385,2250,0.99,1\n2090,385,2251,0.99,1\n2091,386,2253,0.99,1\n2092,386,2255,0.99,1\n2093,387,2257,0.99,1\n2094,387,2259,0.99,1\n2095,387,2261,0.99,1\n2096,387,2263,0.99,1\n2097,388,2267,0.99,1\n2098,388,2271,0.99,1\n2099,388,2275,0.99,1\n2100,388,2279,0.99,1\n2101,388,2283,0.99,1\n2102,388,2287,0.99,1\n2103,389,2293,0.99,1\n2104,389,2299,0.99,1\n2105,389,2305,0.99,1\n2106,389,2311,0.99,1\n2107,389,2317,0.99,1\n2108,389,2323,0.99,1\n2109,389,2329,0.99,1\n2110,389,2335,0.99,1\n2111,389,2341,0.99,1\n2112,390,2350,0.99,1\n2113,390,2359,0.99,1\n2114,390,2368,0.99,1\n2115,390,2377,0.99,1\n2116,390,2386,0.99,1\n2117,390,2395,0.99,1\n2118,390,2404,0.99,1\n2119,390,2413,0.99,1\n2120,390,2422,0.99,1\n2121,390,2431,0.99,1\n2122,390,2440,0.99,1\n2123,390,2449,0.99,1\n2124,390,2458,0.99,1\n2125,390,2467,0.99,1\n2126,391,2481,0.99,1\n2127,392,2482,0.99,1\n2128,392,2483,0.99,1\n2129,393,2485,0.99,1\n2130,393,2487,0.99,1\n2131,394,2489,0.99,1\n2132,394,2491,0.99,1\n2133,394,2493,0.99,1\n2134,394,2495,0.99,1\n2135,395,2499,0.99,1\n2136,395,2503,0.99,1\n2137,395,2507,0.99,1\n2138,395,2511,0.99,1\n2139,395,2515,0.99,1\n2140,395,2519,0.99,1\n2141,396,2525,0.99,1\n2142,396,2531,0.99,1\n2143,396,2537,0.99,1\n2144,396,2543,0.99,1\n2145,396,2549,0.99,1\n2146,396,2555,0.99,1\n2147,396,2561,0.99,1\n2148,396,2567,0.99,1\n2149,396,2573,0.99,1\n2150,397,2582,0.99,1\n2151,397,2591,0.99,1\n2152,397,2600,0.99,1\n2153,397,2609,0.99,1\n2154,397,2618,0.99,1\n2155,397,2627,0.99,1\n2156,397,2636,0.99,1\n2157,397,2645,0.99,1\n2158,397,2654,0.99,1\n2159,397,2663,0.99,1\n2160,397,2672,0.99,1\n2161,397,2681,0.99,1\n2162,397,2690,0.99,1\n2163,397,2699,0.99,1\n2164,398,2713,0.99,1\n2165,399,2714,0.99,1\n2166,399,2715,0.99,1\n2167,400,2717,0.99,1\n2168,400,2719,0.99,1\n2169,401,2721,0.99,1\n2170,401,2723,0.99,1\n2171,401,2725,0.99,1\n2172,401,2727,0.99,1\n2173,402,2731,0.99,1\n2174,402,2735,0.99,1\n2175,402,2739,0.99,1\n2176,402,2743,0.99,1\n2177,402,2747,0.99,1\n2178,402,2751,0.99,1\n2179,403,2757,0.99,1\n2180,403,2763,0.99,1\n2181,403,2769,0.99,1\n2182,403,2775,0.99,1\n2183,403,2781,0.99,1\n2184,403,2787,0.99,1\n2185,403,2793,0.99,1\n2186,403,2799,0.99,1\n2187,403,2805,0.99,1\n2188,404,2814,0.99,1\n2189,404,2823,1.99,1\n2190,404,2832,1.99,1\n2191,404,2841,1.99,1\n2192,404,2850,1.99,1\n2193,404,2859,1.99,1\n2194,404,2868,1.99,1\n2195,404,2877,1.99,1\n2196,404,2886,1.99,1\n2197,404,2895,1.99,1\n2198,404,2904,1.99,1\n2199,404,2913,1.99,1\n2200,404,2922,1.99,1\n2201,404,2931,0.99,1\n2202,405,2945,0.99,1\n2203,406,2946,0.99,1\n2204,406,2947,0.99,1\n2205,407,2949,0.99,1\n2206,407,2951,0.99,1\n2207,408,2953,0.99,1\n2208,408,2955,0.99,1\n2209,408,2957,0.99,1\n2210,408,2959,0.99,1\n2211,409,2963,0.99,1\n2212,409,2967,0.99,1\n2213,409,2971,0.99,1\n2214,409,2975,0.99,1\n2215,409,2979,0.99,1\n2216,409,2983,0.99,1\n2217,410,2989,0.99,1\n2218,410,2995,0.99,1\n2219,410,3001,0.99,1\n2220,410,3007,0.99,1\n2221,410,3013,0.99,1\n2222,410,3019,0.99,1\n2223,410,3025,0.99,1\n2224,410,3031,0.99,1\n2225,410,3037,0.99,1\n2226,411,3046,0.99,1\n2227,411,3055,0.99,1\n2228,411,3064,0.99,1\n2229,411,3073,0.99,1\n2230,411,3082,0.99,1\n2231,411,3091,0.99,1\n2232,411,3100,0.99,1\n2233,411,3109,0.99,1\n2234,411,3118,0.99,1\n2235,411,3127,0.99,1\n2236,411,3136,0.99,1\n2237,411,3145,0.99,1\n2238,411,3154,0.99,1\n2239,411,3163,0.99,1\n2240,412,3177,1.99,1\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/invoices.csv",
    "content": "invoice_id,customer_id,invoice_date,billing_address,billing_city,billing_state,billing_country,billing_postal_code,total\n1,2,2009-01-01T00:00:00.000000000,Theodor-Heuss-Straße 34,Stuttgart,,Germany,70174,1.98\n2,4,2009-01-02T00:00:00.000000000,Ullevålsveien 14,Oslo,,Norway,0171,3.96\n3,8,2009-01-03T00:00:00.000000000,Grétrystraat 63,Brussels,,Belgium,1000,5.94\n4,14,2009-01-06T00:00:00.000000000,8210 111 ST NW,Edmonton,AB,Canada,T6G 2C7,8.91\n5,23,2009-01-11T00:00:00.000000000,69 Salem Street,Boston,MA,USA,2113,13.86\n6,37,2009-01-19T00:00:00.000000000,Berger Straße 10,Frankfurt,,Germany,60316,0.99\n7,38,2009-02-01T00:00:00.000000000,Barbarossastraße 19,Berlin,,Germany,10779,1.98\n8,40,2009-02-01T00:00:00.000000000,\"8, Rue Hanovre\",Paris,,France,75002,1.98\n9,42,2009-02-02T00:00:00.000000000,\"9, Place Louis Barthou\",Bordeaux,,France,33000,3.96\n10,46,2009-02-03T00:00:00.000000000,3 Chatham Street,Dublin,Dublin,Ireland,,5.94\n11,52,2009-02-06T00:00:00.000000000,202 Hoxton Street,London,,United Kingdom,N1 5LH,8.91\n12,2,2009-02-11T00:00:00.000000000,Theodor-Heuss-Straße 34,Stuttgart,,Germany,70174,13.86\n13,16,2009-02-19T00:00:00.000000000,1600 Amphitheatre Parkway,Mountain View,CA,USA,94043-1351,0.99\n14,17,2009-03-04T00:00:00.000000000,1 Microsoft Way,Redmond,WA,USA,98052-8300,1.98\n15,19,2009-03-04T00:00:00.000000000,1 Infinite Loop,Cupertino,CA,USA,95014,1.98\n16,21,2009-03-05T00:00:00.000000000,801 W 4th Street,Reno,NV,USA,89503,3.96\n17,25,2009-03-06T00:00:00.000000000,319 N. Frances Street,Madison,WI,USA,53703,5.94\n18,31,2009-03-09T00:00:00.000000000,194A Chain Lake Drive,Halifax,NS,Canada,B3S 1C5,8.91\n19,40,2009-03-14T00:00:00.000000000,\"8, Rue Hanovre\",Paris,,France,75002,13.86\n20,54,2009-03-22T00:00:00.000000000,110 Raeburn Pl,Edinburgh ,,United Kingdom,EH4 1HH,0.99\n21,55,2009-04-04T00:00:00.000000000,421 Bourke Street,Sidney,NSW,Australia,2010,1.98\n22,57,2009-04-04T00:00:00.000000000,\"Calle Lira, 198\",Santiago,,Chile,,1.98\n23,59,2009-04-05T00:00:00.000000000,\"3,Raj Bhavan Road\",Bangalore,,India,560001,3.96\n24,4,2009-04-06T00:00:00.000000000,Ullevålsveien 14,Oslo,,Norway,0171,5.94\n25,10,2009-04-09T00:00:00.000000000,\"Rua Dr. Falcão Filho, 155\",São Paulo,SP,Brazil,01007-010,8.91\n26,19,2009-04-14T00:00:00.000000000,1 Infinite Loop,Cupertino,CA,USA,95014,13.86\n27,33,2009-04-22T00:00:00.000000000,5112 48 Street,Yellowknife,NT,Canada,X1A 1N6,0.99\n28,34,2009-05-05T00:00:00.000000000,Rua da Assunção 53,Lisbon,,Portugal,,1.98\n29,36,2009-05-05T00:00:00.000000000,Tauentzienstraße 8,Berlin,,Germany,10789,1.98\n30,38,2009-05-06T00:00:00.000000000,Barbarossastraße 19,Berlin,,Germany,10779,3.96\n31,42,2009-05-07T00:00:00.000000000,\"9, Place Louis Barthou\",Bordeaux,,France,33000,5.94\n32,48,2009-05-10T00:00:00.000000000,Lijnbaansgracht 120bg,Amsterdam,VV,Netherlands,1016,8.91\n33,57,2009-05-15T00:00:00.000000000,\"Calle Lira, 198\",Santiago,,Chile,,13.86\n34,12,2009-05-23T00:00:00.000000000,\"Praça Pio X, 119\",Rio de Janeiro,RJ,Brazil,20040-020,0.99\n35,13,2009-06-05T00:00:00.000000000,Qe 7 Bloco G,Brasília,DF,Brazil,71020-677,1.98\n36,15,2009-06-05T00:00:00.000000000,700 W Pender Street,Vancouver,BC,Canada,V6C 1G8,1.98\n37,17,2009-06-06T00:00:00.000000000,1 Microsoft Way,Redmond,WA,USA,98052-8300,3.96\n38,21,2009-06-07T00:00:00.000000000,801 W 4th Street,Reno,NV,USA,89503,5.94\n39,27,2009-06-10T00:00:00.000000000,1033 N Park Ave,Tucson,AZ,USA,85719,8.91\n40,36,2009-06-15T00:00:00.000000000,Tauentzienstraße 8,Berlin,,Germany,10789,13.86\n41,50,2009-06-23T00:00:00.000000000,C/ San Bernardo 85,Madrid,,Spain,28015,0.99\n42,51,2009-07-06T00:00:00.000000000,Celsiusg. 9,Stockholm,,Sweden,11230,1.98\n43,53,2009-07-06T00:00:00.000000000,113 Lupus St,London,,United Kingdom,SW1V 3EN,1.98\n44,55,2009-07-07T00:00:00.000000000,421 Bourke Street,Sidney,NSW,Australia,2010,3.96\n45,59,2009-07-08T00:00:00.000000000,\"3,Raj Bhavan Road\",Bangalore,,India,560001,5.94\n46,6,2009-07-11T00:00:00.000000000,Rilská 3174/6,Prague,,Czech Republic,14300,8.91\n47,15,2009-07-16T00:00:00.000000000,700 W Pender Street,Vancouver,BC,Canada,V6C 1G8,13.86\n48,29,2009-07-24T00:00:00.000000000,796 Dundas Street West,Toronto,ON,Canada,M6J 1V1,0.99\n49,30,2009-08-06T00:00:00.000000000,230 Elgin Street,Ottawa,ON,Canada,K2P 1L7,1.98\n50,32,2009-08-06T00:00:00.000000000,696 Osborne Street,Winnipeg,MB,Canada,R3L 2B9,1.98\n51,34,2009-08-07T00:00:00.000000000,Rua da Assunção 53,Lisbon,,Portugal,,3.96\n52,38,2009-08-08T00:00:00.000000000,Barbarossastraße 19,Berlin,,Germany,10779,5.94\n53,44,2009-08-11T00:00:00.000000000,Porthaninkatu 9,Helsinki,,Finland,00530,8.91\n54,53,2009-08-16T00:00:00.000000000,113 Lupus St,London,,United Kingdom,SW1V 3EN,13.86\n55,8,2009-08-24T00:00:00.000000000,Grétrystraat 63,Brussels,,Belgium,1000,0.99\n56,9,2009-09-06T00:00:00.000000000,Sønder Boulevard 51,Copenhagen,,Denmark,1720,1.98\n57,11,2009-09-06T00:00:00.000000000,\"Av. Paulista, 2022\",São Paulo,SP,Brazil,01310-200,1.98\n58,13,2009-09-07T00:00:00.000000000,Qe 7 Bloco G,Brasília,DF,Brazil,71020-677,3.96\n59,17,2009-09-08T00:00:00.000000000,1 Microsoft Way,Redmond,WA,USA,98052-8300,5.94\n60,23,2009-09-11T00:00:00.000000000,69 Salem Street,Boston,MA,USA,2113,8.91\n61,32,2009-09-16T00:00:00.000000000,696 Osborne Street,Winnipeg,MB,Canada,R3L 2B9,13.86\n62,46,2009-09-24T00:00:00.000000000,3 Chatham Street,Dublin,Dublin,Ireland,,0.99\n63,47,2009-10-07T00:00:00.000000000,\"Via Degli Scipioni, 43\",Rome,RM,Italy,00192,1.98\n64,49,2009-10-07T00:00:00.000000000,Ordynacka 10,Warsaw,,Poland,00-358,1.98\n65,51,2009-10-08T00:00:00.000000000,Celsiusg. 9,Stockholm,,Sweden,11230,3.96\n66,55,2009-10-09T00:00:00.000000000,421 Bourke Street,Sidney,NSW,Australia,2010,5.94\n67,2,2009-10-12T00:00:00.000000000,Theodor-Heuss-Straße 34,Stuttgart,,Germany,70174,8.91\n68,11,2009-10-17T00:00:00.000000000,\"Av. Paulista, 2022\",São Paulo,SP,Brazil,01310-200,13.86\n69,25,2009-10-25T00:00:00.000000000,319 N. Frances Street,Madison,WI,USA,53703,0.99\n70,26,2009-11-07T00:00:00.000000000,2211 W Berry Street,Fort Worth,TX,USA,76110,1.98\n71,28,2009-11-07T00:00:00.000000000,302 S 700 E,Salt Lake City,UT,USA,84102,1.98\n72,30,2009-11-08T00:00:00.000000000,230 Elgin Street,Ottawa,ON,Canada,K2P 1L7,3.96\n73,34,2009-11-09T00:00:00.000000000,Rua da Assunção 53,Lisbon,,Portugal,,5.94\n74,40,2009-11-12T00:00:00.000000000,\"8, Rue Hanovre\",Paris,,France,75002,8.91\n75,49,2009-11-17T00:00:00.000000000,Ordynacka 10,Warsaw,,Poland,00-358,13.86\n76,4,2009-11-25T00:00:00.000000000,Ullevålsveien 14,Oslo,,Norway,0171,0.99\n77,5,2009-12-08T00:00:00.000000000,Klanova 9/506,Prague,,Czech Republic,14700,1.98\n78,7,2009-12-08T00:00:00.000000000,\"Rotenturmstraße 4, 1010 Innere Stadt\",Vienne,,Austria,1010,1.98\n79,9,2009-12-09T00:00:00.000000000,Sønder Boulevard 51,Copenhagen,,Denmark,1720,3.96\n80,13,2009-12-10T00:00:00.000000000,Qe 7 Bloco G,Brasília,DF,Brazil,71020-677,5.94\n81,19,2009-12-13T00:00:00.000000000,1 Infinite Loop,Cupertino,CA,USA,95014,8.91\n82,28,2009-12-18T00:00:00.000000000,302 S 700 E,Salt Lake City,UT,USA,84102,13.86\n83,42,2009-12-26T00:00:00.000000000,\"9, Place Louis Barthou\",Bordeaux,,France,33000,0.99\n84,43,2010-01-08T00:00:00.000000000,\"68, Rue Jouvence\",Dijon,,France,21000,1.98\n85,45,2010-01-08T00:00:00.000000000,Erzsébet krt. 58.,Budapest,,Hungary,H-1073,1.98\n86,47,2010-01-09T00:00:00.000000000,\"Via Degli Scipioni, 43\",Rome,RM,Italy,00192,3.96\n87,51,2010-01-10T00:00:00.000000000,Celsiusg. 9,Stockholm,,Sweden,11230,6.94\n88,57,2010-01-13T00:00:00.000000000,\"Calle Lira, 198\",Santiago,,Chile,,17.91\n89,7,2010-01-18T00:00:00.000000000,\"Rotenturmstraße 4, 1010 Innere Stadt\",Vienne,,Austria,1010,18.86\n90,21,2010-01-26T00:00:00.000000000,801 W 4th Street,Reno,NV,USA,89503,0.99\n91,22,2010-02-08T00:00:00.000000000,120 S Orange Ave,Orlando,FL,USA,32801,1.98\n92,24,2010-02-08T00:00:00.000000000,162 E Superior Street,Chicago,IL,USA,60611,1.98\n93,26,2010-02-09T00:00:00.000000000,2211 W Berry Street,Fort Worth,TX,USA,76110,3.96\n94,30,2010-02-10T00:00:00.000000000,230 Elgin Street,Ottawa,ON,Canada,K2P 1L7,5.94\n95,36,2010-02-13T00:00:00.000000000,Tauentzienstraße 8,Berlin,,Germany,10789,8.91\n96,45,2010-02-18T00:00:00.000000000,Erzsébet krt. 58.,Budapest,,Hungary,H-1073,21.86\n97,59,2010-02-26T00:00:00.000000000,\"3,Raj Bhavan Road\",Bangalore,,India,560001,1.99\n98,1,2010-03-11T00:00:00.000000000,\"Av. Brigadeiro Faria Lima, 2170\",São José dos Campos,SP,Brazil,12227-000,3.98\n99,3,2010-03-11T00:00:00.000000000,1498 rue Bélanger,Montréal,QC,Canada,H2G 1A7,3.98\n100,5,2010-03-12T00:00:00.000000000,Klanova 9/506,Prague,,Czech Republic,14700,3.96\n101,9,2010-03-13T00:00:00.000000000,Sønder Boulevard 51,Copenhagen,,Denmark,1720,5.94\n102,15,2010-03-16T00:00:00.000000000,700 W Pender Street,Vancouver,BC,Canada,V6C 1G8,9.91\n103,24,2010-03-21T00:00:00.000000000,162 E Superior Street,Chicago,IL,USA,60611,15.86\n104,38,2010-03-29T00:00:00.000000000,Barbarossastraße 19,Berlin,,Germany,10779,0.99\n105,39,2010-04-11T00:00:00.000000000,\"4, Rue Milton\",Paris,,France,75009,1.98\n106,41,2010-04-11T00:00:00.000000000,\"11, Place Bellecour\",Lyon,,France,69002,1.98\n107,43,2010-04-12T00:00:00.000000000,\"68, Rue Jouvence\",Dijon,,France,21000,3.96\n108,47,2010-04-13T00:00:00.000000000,\"Via Degli Scipioni, 43\",Rome,RM,Italy,00192,5.94\n109,53,2010-04-16T00:00:00.000000000,113 Lupus St,London,,United Kingdom,SW1V 3EN,8.91\n110,3,2010-04-21T00:00:00.000000000,1498 rue Bélanger,Montréal,QC,Canada,H2G 1A7,13.86\n111,17,2010-04-29T00:00:00.000000000,1 Microsoft Way,Redmond,WA,USA,98052-8300,0.99\n112,18,2010-05-12T00:00:00.000000000,627 Broadway,New York,NY,USA,10012-2612,1.98\n113,20,2010-05-12T00:00:00.000000000,541 Del Medio Avenue,Mountain View,CA,USA,94040-111,1.98\n114,22,2010-05-13T00:00:00.000000000,120 S Orange Ave,Orlando,FL,USA,32801,3.96\n115,26,2010-05-14T00:00:00.000000000,2211 W Berry Street,Fort Worth,TX,USA,76110,5.94\n116,32,2010-05-17T00:00:00.000000000,696 Osborne Street,Winnipeg,MB,Canada,R3L 2B9,8.91\n117,41,2010-05-22T00:00:00.000000000,\"11, Place Bellecour\",Lyon,,France,69002,13.86\n118,55,2010-05-30T00:00:00.000000000,421 Bourke Street,Sidney,NSW,Australia,2010,0.99\n119,56,2010-06-12T00:00:00.000000000,307 Macacha Güemes,Buenos Aires,,Argentina,1106,1.98\n120,58,2010-06-12T00:00:00.000000000,\"12,Community Centre\",Delhi,,India,110017,1.98\n121,1,2010-06-13T00:00:00.000000000,\"Av. Brigadeiro Faria Lima, 2170\",São José dos Campos,SP,Brazil,12227-000,3.96\n122,5,2010-06-14T00:00:00.000000000,Klanova 9/506,Prague,,Czech Republic,14700,5.94\n123,11,2010-06-17T00:00:00.000000000,\"Av. Paulista, 2022\",São Paulo,SP,Brazil,01310-200,8.91\n124,20,2010-06-22T00:00:00.000000000,541 Del Medio Avenue,Mountain View,CA,USA,94040-111,13.86\n125,34,2010-06-30T00:00:00.000000000,Rua da Assunção 53,Lisbon,,Portugal,,0.99\n126,35,2010-07-13T00:00:00.000000000,\"Rua dos Campeões Europeus de Viena, 4350\",Porto,,Portugal,,1.98\n127,37,2010-07-13T00:00:00.000000000,Berger Straße 10,Frankfurt,,Germany,60316,1.98\n128,39,2010-07-14T00:00:00.000000000,\"4, Rue Milton\",Paris,,France,75009,3.96\n129,43,2010-07-15T00:00:00.000000000,\"68, Rue Jouvence\",Dijon,,France,21000,5.94\n130,49,2010-07-18T00:00:00.000000000,Ordynacka 10,Warsaw,,Poland,00-358,8.91\n131,58,2010-07-23T00:00:00.000000000,\"12,Community Centre\",Delhi,,India,110017,13.86\n132,13,2010-07-31T00:00:00.000000000,Qe 7 Bloco G,Brasília,DF,Brazil,71020-677,0.99\n133,14,2010-08-13T00:00:00.000000000,8210 111 ST NW,Edmonton,AB,Canada,T6G 2C7,1.98\n134,16,2010-08-13T00:00:00.000000000,1600 Amphitheatre Parkway,Mountain View,CA,USA,94043-1351,1.98\n135,18,2010-08-14T00:00:00.000000000,627 Broadway,New York,NY,USA,10012-2612,3.96\n136,22,2010-08-15T00:00:00.000000000,120 S Orange Ave,Orlando,FL,USA,32801,5.94\n137,28,2010-08-18T00:00:00.000000000,302 S 700 E,Salt Lake City,UT,USA,84102,8.91\n138,37,2010-08-23T00:00:00.000000000,Berger Straße 10,Frankfurt,,Germany,60316,13.86\n139,51,2010-08-31T00:00:00.000000000,Celsiusg. 9,Stockholm,,Sweden,11230,0.99\n140,52,2010-09-13T00:00:00.000000000,202 Hoxton Street,London,,United Kingdom,N1 5LH,1.98\n141,54,2010-09-13T00:00:00.000000000,110 Raeburn Pl,Edinburgh ,,United Kingdom,EH4 1HH,1.98\n142,56,2010-09-14T00:00:00.000000000,307 Macacha Güemes,Buenos Aires,,Argentina,1106,3.96\n143,1,2010-09-15T00:00:00.000000000,\"Av. Brigadeiro Faria Lima, 2170\",São José dos Campos,SP,Brazil,12227-000,5.94\n144,7,2010-09-18T00:00:00.000000000,\"Rotenturmstraße 4, 1010 Innere Stadt\",Vienne,,Austria,1010,8.91\n145,16,2010-09-23T00:00:00.000000000,1600 Amphitheatre Parkway,Mountain View,CA,USA,94043-1351,13.86\n146,30,2010-10-01T00:00:00.000000000,230 Elgin Street,Ottawa,ON,Canada,K2P 1L7,0.99\n147,31,2010-10-14T00:00:00.000000000,194A Chain Lake Drive,Halifax,NS,Canada,B3S 1C5,1.98\n148,33,2010-10-14T00:00:00.000000000,5112 48 Street,Yellowknife,NT,Canada,X1A 1N6,1.98\n149,35,2010-10-15T00:00:00.000000000,\"Rua dos Campeões Europeus de Viena, 4350\",Porto,,Portugal,,3.96\n150,39,2010-10-16T00:00:00.000000000,\"4, Rue Milton\",Paris,,France,75009,5.94\n151,45,2010-10-19T00:00:00.000000000,Erzsébet krt. 58.,Budapest,,Hungary,H-1073,8.91\n152,54,2010-10-24T00:00:00.000000000,110 Raeburn Pl,Edinburgh ,,United Kingdom,EH4 1HH,13.86\n153,9,2010-11-01T00:00:00.000000000,Sønder Boulevard 51,Copenhagen,,Denmark,1720,0.99\n154,10,2010-11-14T00:00:00.000000000,\"Rua Dr. Falcão Filho, 155\",São Paulo,SP,Brazil,01007-010,1.98\n155,12,2010-11-14T00:00:00.000000000,\"Praça Pio X, 119\",Rio de Janeiro,RJ,Brazil,20040-020,1.98\n156,14,2010-11-15T00:00:00.000000000,8210 111 ST NW,Edmonton,AB,Canada,T6G 2C7,3.96\n157,18,2010-11-16T00:00:00.000000000,627 Broadway,New York,NY,USA,10012-2612,5.94\n158,24,2010-11-19T00:00:00.000000000,162 E Superior Street,Chicago,IL,USA,60611,8.91\n159,33,2010-11-24T00:00:00.000000000,5112 48 Street,Yellowknife,NT,Canada,X1A 1N6,13.86\n160,47,2010-12-02T00:00:00.000000000,\"Via Degli Scipioni, 43\",Rome,RM,Italy,00192,0.99\n161,48,2010-12-15T00:00:00.000000000,Lijnbaansgracht 120bg,Amsterdam,VV,Netherlands,1016,1.98\n162,50,2010-12-15T00:00:00.000000000,C/ San Bernardo 85,Madrid,,Spain,28015,1.98\n163,52,2010-12-16T00:00:00.000000000,202 Hoxton Street,London,,United Kingdom,N1 5LH,3.96\n164,56,2010-12-17T00:00:00.000000000,307 Macacha Güemes,Buenos Aires,,Argentina,1106,5.94\n165,3,2010-12-20T00:00:00.000000000,1498 rue Bélanger,Montréal,QC,Canada,H2G 1A7,8.91\n166,12,2010-12-25T00:00:00.000000000,\"Praça Pio X, 119\",Rio de Janeiro,RJ,Brazil,20040-020,13.86\n167,26,2011-01-02T00:00:00.000000000,2211 W Berry Street,Fort Worth,TX,USA,76110,0.99\n168,27,2011-01-15T00:00:00.000000000,1033 N Park Ave,Tucson,AZ,USA,85719,1.98\n169,29,2011-01-15T00:00:00.000000000,796 Dundas Street West,Toronto,ON,Canada,M6J 1V1,1.98\n170,31,2011-01-16T00:00:00.000000000,194A Chain Lake Drive,Halifax,NS,Canada,B3S 1C5,3.96\n171,35,2011-01-17T00:00:00.000000000,\"Rua dos Campeões Europeus de Viena, 4350\",Porto,,Portugal,,5.94\n172,41,2011-01-20T00:00:00.000000000,\"11, Place Bellecour\",Lyon,,France,69002,8.91\n173,50,2011-01-25T00:00:00.000000000,C/ San Bernardo 85,Madrid,,Spain,28015,13.86\n174,5,2011-02-02T00:00:00.000000000,Klanova 9/506,Prague,,Czech Republic,14700,0.99\n175,6,2011-02-15T00:00:00.000000000,Rilská 3174/6,Prague,,Czech Republic,14300,1.98\n176,8,2011-02-15T00:00:00.000000000,Grétrystraat 63,Brussels,,Belgium,1000,1.98\n177,10,2011-02-16T00:00:00.000000000,\"Rua Dr. Falcão Filho, 155\",São Paulo,SP,Brazil,01007-010,3.96\n178,14,2011-02-17T00:00:00.000000000,8210 111 ST NW,Edmonton,AB,Canada,T6G 2C7,5.94\n179,20,2011-02-20T00:00:00.000000000,541 Del Medio Avenue,Mountain View,CA,USA,94040-111,8.91\n180,29,2011-02-25T00:00:00.000000000,796 Dundas Street West,Toronto,ON,Canada,M6J 1V1,13.86\n181,43,2011-03-05T00:00:00.000000000,\"68, Rue Jouvence\",Dijon,,France,21000,0.99\n182,44,2011-03-18T00:00:00.000000000,Porthaninkatu 9,Helsinki,,Finland,00530,1.98\n183,46,2011-03-18T00:00:00.000000000,3 Chatham Street,Dublin,Dublin,Ireland,,1.98\n184,48,2011-03-19T00:00:00.000000000,Lijnbaansgracht 120bg,Amsterdam,VV,Netherlands,1016,3.96\n185,52,2011-03-20T00:00:00.000000000,202 Hoxton Street,London,,United Kingdom,N1 5LH,5.94\n186,58,2011-03-23T00:00:00.000000000,\"12,Community Centre\",Delhi,,India,110017,8.91\n187,8,2011-03-28T00:00:00.000000000,Grétrystraat 63,Brussels,,Belgium,1000,13.86\n188,22,2011-04-05T00:00:00.000000000,120 S Orange Ave,Orlando,FL,USA,32801,0.99\n189,23,2011-04-18T00:00:00.000000000,69 Salem Street,Boston,MA,USA,2113,1.98\n190,25,2011-04-18T00:00:00.000000000,319 N. Frances Street,Madison,WI,USA,53703,1.98\n191,27,2011-04-19T00:00:00.000000000,1033 N Park Ave,Tucson,AZ,USA,85719,3.96\n192,31,2011-04-20T00:00:00.000000000,194A Chain Lake Drive,Halifax,NS,Canada,B3S 1C5,5.94\n193,37,2011-04-23T00:00:00.000000000,Berger Straße 10,Frankfurt,,Germany,60316,14.91\n194,46,2011-04-28T00:00:00.000000000,3 Chatham Street,Dublin,Dublin,Ireland,,21.86\n195,1,2011-05-06T00:00:00.000000000,\"Av. Brigadeiro Faria Lima, 2170\",São José dos Campos,SP,Brazil,12227-000,0.99\n196,2,2011-05-19T00:00:00.000000000,Theodor-Heuss-Straße 34,Stuttgart,,Germany,70174,1.98\n197,4,2011-05-19T00:00:00.000000000,Ullevålsveien 14,Oslo,,Norway,0171,1.98\n198,6,2011-05-20T00:00:00.000000000,Rilská 3174/6,Prague,,Czech Republic,14300,3.96\n199,10,2011-05-21T00:00:00.000000000,\"Rua Dr. Falcão Filho, 155\",São Paulo,SP,Brazil,01007-010,5.94\n200,16,2011-05-24T00:00:00.000000000,1600 Amphitheatre Parkway,Mountain View,CA,USA,94043-1351,8.91\n201,25,2011-05-29T00:00:00.000000000,319 N. Frances Street,Madison,WI,USA,53703,18.86\n202,39,2011-06-06T00:00:00.000000000,\"4, Rue Milton\",Paris,,France,75009,1.99\n203,40,2011-06-19T00:00:00.000000000,\"8, Rue Hanovre\",Paris,,France,75002,2.98\n204,42,2011-06-19T00:00:00.000000000,\"9, Place Louis Barthou\",Bordeaux,,France,33000,3.98\n205,44,2011-06-20T00:00:00.000000000,Porthaninkatu 9,Helsinki,,Finland,00530,7.96\n206,48,2011-06-21T00:00:00.000000000,Lijnbaansgracht 120bg,Amsterdam,VV,Netherlands,1016,8.94\n207,54,2011-06-24T00:00:00.000000000,110 Raeburn Pl,Edinburgh ,,United Kingdom,EH4 1HH,8.91\n208,4,2011-06-29T00:00:00.000000000,Ullevålsveien 14,Oslo,,Norway,0171,15.86\n209,18,2011-07-07T00:00:00.000000000,627 Broadway,New York,NY,USA,10012-2612,0.99\n210,19,2011-07-20T00:00:00.000000000,1 Infinite Loop,Cupertino,CA,USA,95014,1.98\n211,21,2011-07-20T00:00:00.000000000,801 W 4th Street,Reno,NV,USA,89503,1.98\n212,23,2011-07-21T00:00:00.000000000,69 Salem Street,Boston,MA,USA,2113,3.96\n213,27,2011-07-22T00:00:00.000000000,1033 N Park Ave,Tucson,AZ,USA,85719,5.94\n214,33,2011-07-25T00:00:00.000000000,5112 48 Street,Yellowknife,NT,Canada,X1A 1N6,8.91\n215,42,2011-07-30T00:00:00.000000000,\"9, Place Louis Barthou\",Bordeaux,,France,33000,13.86\n216,56,2011-08-07T00:00:00.000000000,307 Macacha Güemes,Buenos Aires,,Argentina,1106,0.99\n217,57,2011-08-20T00:00:00.000000000,\"Calle Lira, 198\",Santiago,,Chile,,1.98\n218,59,2011-08-20T00:00:00.000000000,\"3,Raj Bhavan Road\",Bangalore,,India,560001,1.98\n219,2,2011-08-21T00:00:00.000000000,Theodor-Heuss-Straße 34,Stuttgart,,Germany,70174,3.96\n220,6,2011-08-22T00:00:00.000000000,Rilská 3174/6,Prague,,Czech Republic,14300,5.94\n221,12,2011-08-25T00:00:00.000000000,\"Praça Pio X, 119\",Rio de Janeiro,RJ,Brazil,20040-020,8.91\n222,21,2011-08-30T00:00:00.000000000,801 W 4th Street,Reno,NV,USA,89503,13.86\n223,35,2011-09-07T00:00:00.000000000,\"Rua dos Campeões Europeus de Viena, 4350\",Porto,,Portugal,,0.99\n224,36,2011-09-20T00:00:00.000000000,Tauentzienstraße 8,Berlin,,Germany,10789,1.98\n225,38,2011-09-20T00:00:00.000000000,Barbarossastraße 19,Berlin,,Germany,10779,1.98\n226,40,2011-09-21T00:00:00.000000000,\"8, Rue Hanovre\",Paris,,France,75002,3.96\n227,44,2011-09-22T00:00:00.000000000,Porthaninkatu 9,Helsinki,,Finland,00530,5.94\n228,50,2011-09-25T00:00:00.000000000,C/ San Bernardo 85,Madrid,,Spain,28015,8.91\n229,59,2011-09-30T00:00:00.000000000,\"3,Raj Bhavan Road\",Bangalore,,India,560001,13.86\n230,14,2011-10-08T00:00:00.000000000,8210 111 ST NW,Edmonton,AB,Canada,T6G 2C7,0.99\n231,15,2011-10-21T00:00:00.000000000,700 W Pender Street,Vancouver,BC,Canada,V6C 1G8,1.98\n232,17,2011-10-21T00:00:00.000000000,1 Microsoft Way,Redmond,WA,USA,98052-8300,1.98\n233,19,2011-10-22T00:00:00.000000000,1 Infinite Loop,Cupertino,CA,USA,95014,3.96\n234,23,2011-10-23T00:00:00.000000000,69 Salem Street,Boston,MA,USA,2113,5.94\n235,29,2011-10-26T00:00:00.000000000,796 Dundas Street West,Toronto,ON,Canada,M6J 1V1,8.91\n236,38,2011-10-31T00:00:00.000000000,Barbarossastraße 19,Berlin,,Germany,10779,13.86\n237,52,2011-11-08T00:00:00.000000000,202 Hoxton Street,London,,United Kingdom,N1 5LH,0.99\n238,53,2011-11-21T00:00:00.000000000,113 Lupus St,London,,United Kingdom,SW1V 3EN,1.98\n239,55,2011-11-21T00:00:00.000000000,421 Bourke Street,Sidney,NSW,Australia,2010,1.98\n240,57,2011-11-22T00:00:00.000000000,\"Calle Lira, 198\",Santiago,,Chile,,3.96\n241,2,2011-11-23T00:00:00.000000000,Theodor-Heuss-Straße 34,Stuttgart,,Germany,70174,5.94\n242,8,2011-11-26T00:00:00.000000000,Grétrystraat 63,Brussels,,Belgium,1000,8.91\n243,17,2011-12-01T00:00:00.000000000,1 Microsoft Way,Redmond,WA,USA,98052-8300,13.86\n244,31,2011-12-09T00:00:00.000000000,194A Chain Lake Drive,Halifax,NS,Canada,B3S 1C5,0.99\n245,32,2011-12-22T00:00:00.000000000,696 Osborne Street,Winnipeg,MB,Canada,R3L 2B9,1.98\n246,34,2011-12-22T00:00:00.000000000,Rua da Assunção 53,Lisbon,,Portugal,,1.98\n247,36,2011-12-23T00:00:00.000000000,Tauentzienstraße 8,Berlin,,Germany,10789,3.96\n248,40,2011-12-24T00:00:00.000000000,\"8, Rue Hanovre\",Paris,,France,75002,5.94\n249,46,2011-12-27T00:00:00.000000000,3 Chatham Street,Dublin,Dublin,Ireland,,8.91\n250,55,2012-01-01T00:00:00.000000000,421 Bourke Street,Sidney,NSW,Australia,2010,13.86\n251,10,2012-01-09T00:00:00.000000000,\"Rua Dr. Falcão Filho, 155\",São Paulo,SP,Brazil,01007-010,0.99\n252,11,2012-01-22T00:00:00.000000000,\"Av. Paulista, 2022\",São Paulo,SP,Brazil,01310-200,1.98\n253,13,2012-01-22T00:00:00.000000000,Qe 7 Bloco G,Brasília,DF,Brazil,71020-677,1.98\n254,15,2012-01-23T00:00:00.000000000,700 W Pender Street,Vancouver,BC,Canada,V6C 1G8,3.96\n255,19,2012-01-24T00:00:00.000000000,1 Infinite Loop,Cupertino,CA,USA,95014,5.94\n256,25,2012-01-27T00:00:00.000000000,319 N. Frances Street,Madison,WI,USA,53703,8.91\n257,34,2012-02-01T00:00:00.000000000,Rua da Assunção 53,Lisbon,,Portugal,,13.86\n258,48,2012-02-09T00:00:00.000000000,Lijnbaansgracht 120bg,Amsterdam,VV,Netherlands,1016,0.99\n259,49,2012-02-22T00:00:00.000000000,Ordynacka 10,Warsaw,,Poland,00-358,1.98\n260,51,2012-02-22T00:00:00.000000000,Celsiusg. 9,Stockholm,,Sweden,11230,1.98\n261,53,2012-02-23T00:00:00.000000000,113 Lupus St,London,,United Kingdom,SW1V 3EN,3.96\n262,57,2012-02-24T00:00:00.000000000,\"Calle Lira, 198\",Santiago,,Chile,,5.94\n263,4,2012-02-27T00:00:00.000000000,Ullevålsveien 14,Oslo,,Norway,0171,8.91\n264,13,2012-03-03T00:00:00.000000000,Qe 7 Bloco G,Brasília,DF,Brazil,71020-677,13.86\n265,27,2012-03-11T00:00:00.000000000,1033 N Park Ave,Tucson,AZ,USA,85719,0.99\n266,28,2012-03-24T00:00:00.000000000,302 S 700 E,Salt Lake City,UT,USA,84102,1.98\n267,30,2012-03-24T00:00:00.000000000,230 Elgin Street,Ottawa,ON,Canada,K2P 1L7,1.98\n268,32,2012-03-25T00:00:00.000000000,696 Osborne Street,Winnipeg,MB,Canada,R3L 2B9,3.96\n269,36,2012-03-26T00:00:00.000000000,Tauentzienstraße 8,Berlin,,Germany,10789,5.94\n270,42,2012-03-29T00:00:00.000000000,\"9, Place Louis Barthou\",Bordeaux,,France,33000,8.91\n271,51,2012-04-03T00:00:00.000000000,Celsiusg. 9,Stockholm,,Sweden,11230,13.86\n272,6,2012-04-11T00:00:00.000000000,Rilská 3174/6,Prague,,Czech Republic,14300,0.99\n273,7,2012-04-24T00:00:00.000000000,\"Rotenturmstraße 4, 1010 Innere Stadt\",Vienne,,Austria,1010,1.98\n274,9,2012-04-24T00:00:00.000000000,Sønder Boulevard 51,Copenhagen,,Denmark,1720,1.98\n275,11,2012-04-25T00:00:00.000000000,\"Av. Paulista, 2022\",São Paulo,SP,Brazil,01310-200,3.96\n276,15,2012-04-26T00:00:00.000000000,700 W Pender Street,Vancouver,BC,Canada,V6C 1G8,5.94\n277,21,2012-04-29T00:00:00.000000000,801 W 4th Street,Reno,NV,USA,89503,8.91\n278,30,2012-05-04T00:00:00.000000000,230 Elgin Street,Ottawa,ON,Canada,K2P 1L7,13.86\n279,44,2012-05-12T00:00:00.000000000,Porthaninkatu 9,Helsinki,,Finland,00530,0.99\n280,45,2012-05-25T00:00:00.000000000,Erzsébet krt. 58.,Budapest,,Hungary,H-1073,1.98\n281,47,2012-05-25T00:00:00.000000000,\"Via Degli Scipioni, 43\",Rome,RM,Italy,00192,1.98\n282,49,2012-05-26T00:00:00.000000000,Ordynacka 10,Warsaw,,Poland,00-358,3.96\n283,53,2012-05-27T00:00:00.000000000,113 Lupus St,London,,United Kingdom,SW1V 3EN,5.94\n284,59,2012-05-30T00:00:00.000000000,\"3,Raj Bhavan Road\",Bangalore,,India,560001,8.91\n285,9,2012-06-04T00:00:00.000000000,Sønder Boulevard 51,Copenhagen,,Denmark,1720,13.86\n286,23,2012-06-12T00:00:00.000000000,69 Salem Street,Boston,MA,USA,2113,0.99\n287,24,2012-06-25T00:00:00.000000000,162 E Superior Street,Chicago,IL,USA,60611,1.98\n288,26,2012-06-25T00:00:00.000000000,2211 W Berry Street,Fort Worth,TX,USA,76110,1.98\n289,28,2012-06-26T00:00:00.000000000,302 S 700 E,Salt Lake City,UT,USA,84102,3.96\n290,32,2012-06-27T00:00:00.000000000,696 Osborne Street,Winnipeg,MB,Canada,R3L 2B9,5.94\n291,38,2012-06-30T00:00:00.000000000,Barbarossastraße 19,Berlin,,Germany,10779,8.91\n292,47,2012-07-05T00:00:00.000000000,\"Via Degli Scipioni, 43\",Rome,RM,Italy,00192,13.86\n293,2,2012-07-13T00:00:00.000000000,Theodor-Heuss-Straße 34,Stuttgart,,Germany,70174,0.99\n294,3,2012-07-26T00:00:00.000000000,1498 rue Bélanger,Montréal,QC,Canada,H2G 1A7,1.98\n295,5,2012-07-26T00:00:00.000000000,Klanova 9/506,Prague,,Czech Republic,14700,1.98\n296,7,2012-07-27T00:00:00.000000000,\"Rotenturmstraße 4, 1010 Innere Stadt\",Vienne,,Austria,1010,3.96\n297,11,2012-07-28T00:00:00.000000000,\"Av. Paulista, 2022\",São Paulo,SP,Brazil,01310-200,5.94\n298,17,2012-07-31T00:00:00.000000000,1 Microsoft Way,Redmond,WA,USA,98052-8300,10.91\n299,26,2012-08-05T00:00:00.000000000,2211 W Berry Street,Fort Worth,TX,USA,76110,23.86\n300,40,2012-08-13T00:00:00.000000000,\"8, Rue Hanovre\",Paris,,France,75002,0.99\n301,41,2012-08-26T00:00:00.000000000,\"11, Place Bellecour\",Lyon,,France,69002,1.98\n302,43,2012-08-26T00:00:00.000000000,\"68, Rue Jouvence\",Dijon,,France,21000,1.98\n303,45,2012-08-27T00:00:00.000000000,Erzsébet krt. 58.,Budapest,,Hungary,H-1073,3.96\n304,49,2012-08-28T00:00:00.000000000,Ordynacka 10,Warsaw,,Poland,00-358,5.94\n305,55,2012-08-31T00:00:00.000000000,421 Bourke Street,Sidney,NSW,Australia,2010,8.91\n306,5,2012-09-05T00:00:00.000000000,Klanova 9/506,Prague,,Czech Republic,14700,16.86\n307,19,2012-09-13T00:00:00.000000000,1 Infinite Loop,Cupertino,CA,USA,95014,1.99\n308,20,2012-09-26T00:00:00.000000000,541 Del Medio Avenue,Mountain View,CA,USA,94040-111,3.98\n309,22,2012-09-26T00:00:00.000000000,120 S Orange Ave,Orlando,FL,USA,32801,3.98\n310,24,2012-09-27T00:00:00.000000000,162 E Superior Street,Chicago,IL,USA,60611,7.96\n311,28,2012-09-28T00:00:00.000000000,302 S 700 E,Salt Lake City,UT,USA,84102,11.94\n312,34,2012-10-01T00:00:00.000000000,Rua da Assunção 53,Lisbon,,Portugal,,10.91\n313,43,2012-10-06T00:00:00.000000000,\"68, Rue Jouvence\",Dijon,,France,21000,16.86\n314,57,2012-10-14T00:00:00.000000000,\"Calle Lira, 198\",Santiago,,Chile,,0.99\n315,58,2012-10-27T00:00:00.000000000,\"12,Community Centre\",Delhi,,India,110017,1.98\n316,1,2012-10-27T00:00:00.000000000,\"Av. Brigadeiro Faria Lima, 2170\",São José dos Campos,SP,Brazil,12227-000,1.98\n317,3,2012-10-28T00:00:00.000000000,1498 rue Bélanger,Montréal,QC,Canada,H2G 1A7,3.96\n318,7,2012-10-29T00:00:00.000000000,\"Rotenturmstraße 4, 1010 Innere Stadt\",Vienne,,Austria,1010,5.94\n319,13,2012-11-01T00:00:00.000000000,Qe 7 Bloco G,Brasília,DF,Brazil,71020-677,8.91\n320,22,2012-11-06T00:00:00.000000000,120 S Orange Ave,Orlando,FL,USA,32801,13.86\n321,36,2012-11-14T00:00:00.000000000,Tauentzienstraße 8,Berlin,,Germany,10789,0.99\n322,37,2012-11-27T00:00:00.000000000,Berger Straße 10,Frankfurt,,Germany,60316,1.98\n323,39,2012-11-27T00:00:00.000000000,\"4, Rue Milton\",Paris,,France,75009,1.98\n324,41,2012-11-28T00:00:00.000000000,\"11, Place Bellecour\",Lyon,,France,69002,3.96\n325,45,2012-11-29T00:00:00.000000000,Erzsébet krt. 58.,Budapest,,Hungary,H-1073,5.94\n326,51,2012-12-02T00:00:00.000000000,Celsiusg. 9,Stockholm,,Sweden,11230,8.91\n327,1,2012-12-07T00:00:00.000000000,\"Av. Brigadeiro Faria Lima, 2170\",São José dos Campos,SP,Brazil,12227-000,13.86\n328,15,2012-12-15T00:00:00.000000000,700 W Pender Street,Vancouver,BC,Canada,V6C 1G8,0.99\n329,16,2012-12-28T00:00:00.000000000,1600 Amphitheatre Parkway,Mountain View,CA,USA,94043-1351,1.98\n330,18,2012-12-28T00:00:00.000000000,627 Broadway,New York,NY,USA,10012-2612,1.98\n331,20,2012-12-29T00:00:00.000000000,541 Del Medio Avenue,Mountain View,CA,USA,94040-111,3.96\n332,24,2012-12-30T00:00:00.000000000,162 E Superior Street,Chicago,IL,USA,60611,5.94\n333,30,2013-01-02T00:00:00.000000000,230 Elgin Street,Ottawa,ON,Canada,K2P 1L7,8.91\n334,39,2013-01-07T00:00:00.000000000,\"4, Rue Milton\",Paris,,France,75009,13.86\n335,53,2013-01-15T00:00:00.000000000,113 Lupus St,London,,United Kingdom,SW1V 3EN,0.99\n336,54,2013-01-28T00:00:00.000000000,110 Raeburn Pl,Edinburgh ,,United Kingdom,EH4 1HH,1.98\n337,56,2013-01-28T00:00:00.000000000,307 Macacha Güemes,Buenos Aires,,Argentina,1106,1.98\n338,58,2013-01-29T00:00:00.000000000,\"12,Community Centre\",Delhi,,India,110017,3.96\n339,3,2013-01-30T00:00:00.000000000,1498 rue Bélanger,Montréal,QC,Canada,H2G 1A7,5.94\n340,9,2013-02-02T00:00:00.000000000,Sønder Boulevard 51,Copenhagen,,Denmark,1720,8.91\n341,18,2013-02-07T00:00:00.000000000,627 Broadway,New York,NY,USA,10012-2612,13.86\n342,32,2013-02-15T00:00:00.000000000,696 Osborne Street,Winnipeg,MB,Canada,R3L 2B9,0.99\n343,33,2013-02-28T00:00:00.000000000,5112 48 Street,Yellowknife,NT,Canada,X1A 1N6,1.98\n344,35,2013-02-28T00:00:00.000000000,\"Rua dos Campeões Europeus de Viena, 4350\",Porto,,Portugal,,1.98\n345,37,2013-03-01T00:00:00.000000000,Berger Straße 10,Frankfurt,,Germany,60316,3.96\n346,41,2013-03-02T00:00:00.000000000,\"11, Place Bellecour\",Lyon,,France,69002,5.94\n347,47,2013-03-05T00:00:00.000000000,\"Via Degli Scipioni, 43\",Rome,RM,Italy,00192,8.91\n348,56,2013-03-10T00:00:00.000000000,307 Macacha Güemes,Buenos Aires,,Argentina,1106,13.86\n349,11,2013-03-18T00:00:00.000000000,\"Av. Paulista, 2022\",São Paulo,SP,Brazil,01310-200,0.99\n350,12,2013-03-31T00:00:00.000000000,\"Praça Pio X, 119\",Rio de Janeiro,RJ,Brazil,20040-020,1.98\n351,14,2013-03-31T00:00:00.000000000,8210 111 ST NW,Edmonton,AB,Canada,T6G 2C7,1.98\n352,16,2013-04-01T00:00:00.000000000,1600 Amphitheatre Parkway,Mountain View,CA,USA,94043-1351,3.96\n353,20,2013-04-02T00:00:00.000000000,541 Del Medio Avenue,Mountain View,CA,USA,94040-111,5.94\n354,26,2013-04-05T00:00:00.000000000,2211 W Berry Street,Fort Worth,TX,USA,76110,8.91\n355,35,2013-04-10T00:00:00.000000000,\"Rua dos Campeões Europeus de Viena, 4350\",Porto,,Portugal,,13.86\n356,49,2013-04-18T00:00:00.000000000,Ordynacka 10,Warsaw,,Poland,00-358,0.99\n357,50,2013-05-01T00:00:00.000000000,C/ San Bernardo 85,Madrid,,Spain,28015,1.98\n358,52,2013-05-01T00:00:00.000000000,202 Hoxton Street,London,,United Kingdom,N1 5LH,1.98\n359,54,2013-05-02T00:00:00.000000000,110 Raeburn Pl,Edinburgh ,,United Kingdom,EH4 1HH,3.96\n360,58,2013-05-03T00:00:00.000000000,\"12,Community Centre\",Delhi,,India,110017,5.94\n361,5,2013-05-06T00:00:00.000000000,Klanova 9/506,Prague,,Czech Republic,14700,8.91\n362,14,2013-05-11T00:00:00.000000000,8210 111 ST NW,Edmonton,AB,Canada,T6G 2C7,13.86\n363,28,2013-05-19T00:00:00.000000000,302 S 700 E,Salt Lake City,UT,USA,84102,0.99\n364,29,2013-06-01T00:00:00.000000000,796 Dundas Street West,Toronto,ON,Canada,M6J 1V1,1.98\n365,31,2013-06-01T00:00:00.000000000,194A Chain Lake Drive,Halifax,NS,Canada,B3S 1C5,1.98\n366,33,2013-06-02T00:00:00.000000000,5112 48 Street,Yellowknife,NT,Canada,X1A 1N6,3.96\n367,37,2013-06-03T00:00:00.000000000,Berger Straße 10,Frankfurt,,Germany,60316,5.94\n368,43,2013-06-06T00:00:00.000000000,\"68, Rue Jouvence\",Dijon,,France,21000,8.91\n369,52,2013-06-11T00:00:00.000000000,202 Hoxton Street,London,,United Kingdom,N1 5LH,13.86\n370,7,2013-06-19T00:00:00.000000000,\"Rotenturmstraße 4, 1010 Innere Stadt\",Vienne,,Austria,1010,0.99\n371,8,2013-07-02T00:00:00.000000000,Grétrystraat 63,Brussels,,Belgium,1000,1.98\n372,10,2013-07-02T00:00:00.000000000,\"Rua Dr. Falcão Filho, 155\",São Paulo,SP,Brazil,01007-010,1.98\n373,12,2013-07-03T00:00:00.000000000,\"Praça Pio X, 119\",Rio de Janeiro,RJ,Brazil,20040-020,3.96\n374,16,2013-07-04T00:00:00.000000000,1600 Amphitheatre Parkway,Mountain View,CA,USA,94043-1351,5.94\n375,22,2013-07-07T00:00:00.000000000,120 S Orange Ave,Orlando,FL,USA,32801,8.91\n376,31,2013-07-12T00:00:00.000000000,194A Chain Lake Drive,Halifax,NS,Canada,B3S 1C5,13.86\n377,45,2013-07-20T00:00:00.000000000,Erzsébet krt. 58.,Budapest,,Hungary,H-1073,0.99\n378,46,2013-08-02T00:00:00.000000000,3 Chatham Street,Dublin,Dublin,Ireland,,1.98\n379,48,2013-08-02T00:00:00.000000000,Lijnbaansgracht 120bg,Amsterdam,VV,Netherlands,1016,1.98\n380,50,2013-08-03T00:00:00.000000000,C/ San Bernardo 85,Madrid,,Spain,28015,3.96\n381,54,2013-08-04T00:00:00.000000000,110 Raeburn Pl,Edinburgh ,,United Kingdom,EH4 1HH,5.94\n382,1,2013-08-07T00:00:00.000000000,\"Av. Brigadeiro Faria Lima, 2170\",São José dos Campos,SP,Brazil,12227-000,8.91\n383,10,2013-08-12T00:00:00.000000000,\"Rua Dr. Falcão Filho, 155\",São Paulo,SP,Brazil,01007-010,13.86\n384,24,2013-08-20T00:00:00.000000000,162 E Superior Street,Chicago,IL,USA,60611,0.99\n385,25,2013-09-02T00:00:00.000000000,319 N. Frances Street,Madison,WI,USA,53703,1.98\n386,27,2013-09-02T00:00:00.000000000,1033 N Park Ave,Tucson,AZ,USA,85719,1.98\n387,29,2013-09-03T00:00:00.000000000,796 Dundas Street West,Toronto,ON,Canada,M6J 1V1,3.96\n388,33,2013-09-04T00:00:00.000000000,5112 48 Street,Yellowknife,NT,Canada,X1A 1N6,5.94\n389,39,2013-09-07T00:00:00.000000000,\"4, Rue Milton\",Paris,,France,75009,8.91\n390,48,2013-09-12T00:00:00.000000000,Lijnbaansgracht 120bg,Amsterdam,VV,Netherlands,1016,13.86\n391,3,2013-09-20T00:00:00.000000000,1498 rue Bélanger,Montréal,QC,Canada,H2G 1A7,0.99\n392,4,2013-10-03T00:00:00.000000000,Ullevålsveien 14,Oslo,,Norway,0171,1.98\n393,6,2013-10-03T00:00:00.000000000,Rilská 3174/6,Prague,,Czech Republic,14300,1.98\n394,8,2013-10-04T00:00:00.000000000,Grétrystraat 63,Brussels,,Belgium,1000,3.96\n395,12,2013-10-05T00:00:00.000000000,\"Praça Pio X, 119\",Rio de Janeiro,RJ,Brazil,20040-020,5.94\n396,18,2013-10-08T00:00:00.000000000,627 Broadway,New York,NY,USA,10012-2612,8.91\n397,27,2013-10-13T00:00:00.000000000,1033 N Park Ave,Tucson,AZ,USA,85719,13.86\n398,41,2013-10-21T00:00:00.000000000,\"11, Place Bellecour\",Lyon,,France,69002,0.99\n399,42,2013-11-03T00:00:00.000000000,\"9, Place Louis Barthou\",Bordeaux,,France,33000,1.98\n400,44,2013-11-03T00:00:00.000000000,Porthaninkatu 9,Helsinki,,Finland,00530,1.98\n401,46,2013-11-04T00:00:00.000000000,3 Chatham Street,Dublin,Dublin,Ireland,,3.96\n402,50,2013-11-05T00:00:00.000000000,C/ San Bernardo 85,Madrid,,Spain,28015,5.94\n403,56,2013-11-08T00:00:00.000000000,307 Macacha Güemes,Buenos Aires,,Argentina,1106,8.91\n404,6,2013-11-13T00:00:00.000000000,Rilská 3174/6,Prague,,Czech Republic,14300,25.86\n405,20,2013-11-21T00:00:00.000000000,541 Del Medio Avenue,Mountain View,CA,USA,94040-111,0.99\n406,21,2013-12-04T00:00:00.000000000,801 W 4th Street,Reno,NV,USA,89503,1.98\n407,23,2013-12-04T00:00:00.000000000,69 Salem Street,Boston,MA,USA,2113,1.98\n408,25,2013-12-05T00:00:00.000000000,319 N. Frances Street,Madison,WI,USA,53703,3.96\n409,29,2013-12-06T00:00:00.000000000,796 Dundas Street West,Toronto,ON,Canada,M6J 1V1,5.94\n410,35,2013-12-09T00:00:00.000000000,\"Rua dos Campeões Europeus de Viena, 4350\",Porto,,Portugal,,8.91\n411,44,2013-12-14T00:00:00.000000000,Porthaninkatu 9,Helsinki,,Finland,00530,13.86\n412,58,2013-12-22T00:00:00.000000000,\"12,Community Centre\",Delhi,,India,110017,1.99\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/media_types.csv",
    "content": "media_type_id,name\n1,MPEG audio file\n2,Protected AAC audio file\n3,Protected MPEG-4 video file\n4,Purchased AAC audio file\n5,AAC audio file\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/media_types.json",
    "content": "[\n  { \"media_type_id\": 1, \"name\": \"MPEG audio file\" },\n  { \"media_type_id\": 2, \"name\": \"Protected AAC audio file\" },\n  { \"media_type_id\": 3, \"name\": \"Protected MPEG-4 video file\" },\n  { \"media_type_id\": 4, \"name\": \"Purchased AAC audio file\" },\n  { \"media_type_id\": 5, \"name\": \"AAC audio file\" }\n]\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/playlist_track.csv",
    "content": "playlist_id,track_id\n1,3402\n1,3389\n1,3390\n1,3391\n1,3392\n1,3393\n1,3394\n1,3395\n1,3396\n1,3397\n1,3398\n1,3399\n1,3400\n1,3401\n1,3336\n1,3478\n1,3375\n1,3376\n1,3377\n1,3378\n1,3379\n1,3380\n1,3381\n1,3382\n1,3383\n1,3384\n1,3385\n1,3386\n1,3387\n1,3388\n1,3365\n1,3366\n1,3367\n1,3368\n1,3369\n1,3370\n1,3371\n1,3372\n1,3373\n1,3374\n1,99\n1,100\n1,101\n1,102\n1,103\n1,104\n1,105\n1,106\n1,107\n1,108\n1,109\n1,110\n1,166\n1,167\n1,168\n1,169\n1,170\n1,171\n1,172\n1,173\n1,174\n1,175\n1,176\n1,177\n1,178\n1,179\n1,180\n1,181\n1,182\n1,2591\n1,2592\n1,2593\n1,2594\n1,2595\n1,2596\n1,2597\n1,2598\n1,2599\n1,2600\n1,2601\n1,2602\n1,2603\n1,2604\n1,2605\n1,2606\n1,2607\n1,2608\n1,923\n1,924\n1,925\n1,926\n1,927\n1,928\n1,929\n1,930\n1,931\n1,932\n1,933\n1,934\n1,935\n1,936\n1,937\n1,938\n1,939\n1,940\n1,941\n1,942\n1,943\n1,944\n1,945\n1,946\n1,947\n1,948\n1,964\n1,965\n1,966\n1,967\n1,968\n1,969\n1,970\n1,971\n1,972\n1,973\n1,974\n1,1009\n1,1010\n1,1011\n1,1012\n1,1013\n1,1014\n1,1015\n1,1016\n1,1017\n1,1018\n1,1019\n1,1133\n1,1134\n1,1135\n1,1136\n1,1137\n1,1138\n1,1139\n1,1140\n1,1141\n1,1142\n1,1143\n1,1144\n1,1145\n1,468\n1,469\n1,470\n1,471\n1,472\n1,473\n1,474\n1,475\n1,476\n1,477\n1,478\n1,479\n1,480\n1,481\n1,482\n1,483\n1,484\n1,485\n1,486\n1,487\n1,488\n1,1466\n1,1467\n1,1468\n1,1469\n1,1470\n1,1471\n1,1472\n1,1473\n1,1474\n1,1475\n1,1476\n1,1477\n1,1478\n1,529\n1,530\n1,531\n1,532\n1,533\n1,534\n1,535\n1,536\n1,537\n1,538\n1,539\n1,540\n1,541\n1,542\n1,2165\n1,2166\n1,2167\n1,2168\n1,2169\n1,2170\n1,2171\n1,2172\n1,2173\n1,2174\n1,2175\n1,2176\n1,2177\n1,2318\n1,2319\n1,2320\n1,2321\n1,2322\n1,2323\n1,2324\n1,2325\n1,2326\n1,2327\n1,2328\n1,2329\n1,2330\n1,2331\n1,2332\n1,2333\n1,2285\n1,2286\n1,2287\n1,2288\n1,2289\n1,2290\n1,2291\n1,2292\n1,2293\n1,2294\n1,2295\n1,2310\n1,2311\n1,2312\n1,2313\n1,2314\n1,2315\n1,2316\n1,2317\n1,2282\n1,2283\n1,2284\n1,2334\n1,2335\n1,2336\n1,2337\n1,2338\n1,2339\n1,2340\n1,2341\n1,2342\n1,2343\n1,2358\n1,2359\n1,2360\n1,2361\n1,2362\n1,2363\n1,2364\n1,2365\n1,2366\n1,2367\n1,2368\n1,2369\n1,2370\n1,2371\n1,2372\n1,2373\n1,2374\n1,2472\n1,2473\n1,2474\n1,2475\n1,2476\n1,2477\n1,2478\n1,2479\n1,2480\n1,2481\n1,2482\n1,2483\n1,2484\n1,2485\n1,2486\n1,2487\n1,2488\n1,2489\n1,2490\n1,2491\n1,2492\n1,2493\n1,2494\n1,2495\n1,2496\n1,2497\n1,2498\n1,2499\n1,2500\n1,2501\n1,2502\n1,2503\n1,2504\n1,2505\n1,2705\n1,2706\n1,2707\n1,2708\n1,2709\n1,2710\n1,2711\n1,2712\n1,2713\n1,2714\n1,2715\n1,2716\n1,2717\n1,2718\n1,2719\n1,2720\n1,2721\n1,2722\n1,2723\n1,2724\n1,2725\n1,2726\n1,2727\n1,2728\n1,2729\n1,2730\n1,2781\n1,2782\n1,2783\n1,2784\n1,2785\n1,2786\n1,2787\n1,2788\n1,2789\n1,2790\n1,2791\n1,2792\n1,2793\n1,2794\n1,2795\n1,2796\n1,2797\n1,2798\n1,2799\n1,2800\n1,2801\n1,2802\n1,2803\n1,2804\n1,2805\n1,2806\n1,2807\n1,2808\n1,2809\n1,2810\n1,2811\n1,2812\n1,2813\n1,2814\n1,2815\n1,2816\n1,2817\n1,2818\n1,2572\n1,2573\n1,2574\n1,2575\n1,2576\n1,2577\n1,2578\n1,2579\n1,2580\n1,2581\n1,2582\n1,2583\n1,2584\n1,2585\n1,2586\n1,2587\n1,2588\n1,2589\n1,2590\n1,194\n1,195\n1,196\n1,197\n1,198\n1,199\n1,200\n1,201\n1,202\n1,203\n1,204\n1,891\n1,892\n1,893\n1,894\n1,895\n1,896\n1,897\n1,898\n1,899\n1,900\n1,901\n1,902\n1,903\n1,904\n1,905\n1,906\n1,907\n1,908\n1,909\n1,910\n1,911\n1,912\n1,913\n1,914\n1,915\n1,916\n1,917\n1,918\n1,919\n1,920\n1,921\n1,922\n1,1268\n1,1269\n1,1270\n1,1271\n1,1272\n1,1273\n1,1274\n1,1275\n1,1276\n1,2532\n1,2533\n1,2534\n1,2535\n1,2536\n1,2537\n1,2538\n1,2539\n1,2540\n1,2541\n1,646\n1,647\n1,648\n1,649\n1,651\n1,653\n1,655\n1,658\n1,652\n1,656\n1,657\n1,650\n1,659\n1,654\n1,660\n1,3427\n1,3411\n1,3412\n1,3419\n1,3482\n1,3438\n1,3485\n1,3403\n1,3406\n1,3442\n1,3421\n1,3436\n1,3450\n1,3454\n1,3491\n1,3413\n1,3426\n1,3416\n1,3501\n1,3487\n1,3417\n1,3432\n1,3443\n1,3447\n1,3452\n1,3441\n1,3434\n1,3500\n1,3449\n1,3405\n1,3488\n1,3423\n1,3499\n1,3445\n1,3440\n1,3453\n1,3497\n1,3494\n1,3439\n1,3422\n1,3407\n1,3495\n1,3435\n1,3490\n1,3489\n1,3448\n1,3492\n1,3425\n1,3483\n1,3420\n1,3424\n1,3493\n1,3437\n1,3498\n1,3446\n1,3444\n1,3496\n1,3502\n1,3359\n1,3433\n1,3415\n1,3479\n1,3481\n1,3404\n1,3486\n1,3414\n1,3410\n1,3431\n1,3418\n1,3430\n1,3408\n1,3480\n1,3409\n1,3484\n1,1033\n1,1034\n1,1035\n1,1036\n1,1037\n1,1038\n1,1039\n1,1040\n1,1041\n1,1042\n1,1043\n1,1044\n1,1045\n1,1046\n1,1047\n1,1048\n1,1049\n1,1050\n1,1051\n1,1052\n1,1053\n1,1054\n1,1055\n1,1056\n1,3324\n1,3331\n1,3332\n1,3322\n1,3329\n1,1455\n1,1456\n1,1457\n1,1458\n1,1459\n1,1460\n1,1461\n1,1462\n1,1463\n1,1464\n1,1465\n1,3352\n1,3358\n1,3326\n1,3327\n1,3330\n1,3321\n1,3319\n1,3328\n1,3325\n1,3323\n1,3334\n1,3333\n1,3335\n1,3320\n1,1245\n1,1246\n1,1247\n1,1248\n1,1249\n1,1250\n1,1251\n1,1252\n1,1253\n1,1254\n1,1255\n1,1277\n1,1278\n1,1279\n1,1280\n1,1281\n1,1282\n1,1283\n1,1284\n1,1285\n1,1286\n1,1287\n1,1288\n1,1300\n1,1301\n1,1302\n1,1303\n1,1304\n1,3301\n1,3300\n1,3302\n1,3303\n1,3304\n1,3305\n1,3306\n1,3307\n1,3308\n1,3309\n1,3310\n1,3311\n1,3312\n1,3313\n1,3314\n1,3315\n1,3316\n1,3317\n1,3318\n1,2238\n1,2239\n1,2240\n1,2241\n1,2242\n1,2243\n1,2244\n1,2245\n1,2246\n1,2247\n1,2248\n1,2249\n1,2250\n1,2251\n1,2252\n1,2253\n1,3357\n1,3350\n1,3349\n1,63\n1,64\n1,65\n1,66\n1,67\n1,68\n1,69\n1,70\n1,71\n1,72\n1,73\n1,74\n1,75\n1,76\n1,123\n1,124\n1,125\n1,126\n1,127\n1,128\n1,129\n1,130\n1,842\n1,843\n1,844\n1,845\n1,846\n1,847\n1,848\n1,849\n1,850\n1,624\n1,625\n1,626\n1,627\n1,628\n1,629\n1,630\n1,631\n1,632\n1,633\n1,634\n1,635\n1,636\n1,637\n1,638\n1,639\n1,640\n1,641\n1,642\n1,643\n1,644\n1,645\n1,1102\n1,1103\n1,1104\n1,1188\n1,1189\n1,1190\n1,1191\n1,1192\n1,1193\n1,1194\n1,1195\n1,1196\n1,1197\n1,1198\n1,1199\n1,1200\n1,597\n1,598\n1,599\n1,600\n1,601\n1,602\n1,603\n1,604\n1,605\n1,606\n1,607\n1,608\n1,609\n1,610\n1,611\n1,612\n1,613\n1,614\n1,615\n1,616\n1,617\n1,618\n1,619\n1,1902\n1,1903\n1,1904\n1,1905\n1,1906\n1,1907\n1,1908\n1,1909\n1,1910\n1,1911\n1,1912\n1,1913\n1,1914\n1,1915\n1,456\n1,457\n1,458\n1,459\n1,460\n1,461\n1,462\n1,463\n1,464\n1,465\n1,466\n1,467\n1,2523\n1,2524\n1,2525\n1,2526\n1,2527\n1,2528\n1,2529\n1,2530\n1,2531\n1,379\n1,391\n1,376\n1,397\n1,382\n1,389\n1,404\n1,406\n1,380\n1,394\n1,515\n1,516\n1,517\n1,518\n1,519\n1,520\n1,521\n1,522\n1,523\n1,524\n1,525\n1,526\n1,527\n1,528\n1,205\n1,206\n1,207\n1,208\n1,209\n1,210\n1,211\n1,212\n1,213\n1,214\n1,215\n1,216\n1,217\n1,218\n1,219\n1,220\n1,221\n1,222\n1,223\n1,224\n1,225\n1,715\n1,716\n1,717\n1,718\n1,719\n1,720\n1,721\n1,722\n1,723\n1,724\n1,725\n1,726\n1,727\n1,728\n1,729\n1,730\n1,731\n1,732\n1,733\n1,734\n1,735\n1,736\n1,737\n1,738\n1,739\n1,740\n1,741\n1,742\n1,743\n1,744\n1,226\n1,227\n1,228\n1,229\n1,230\n1,231\n1,232\n1,233\n1,234\n1,235\n1,236\n1,237\n1,238\n1,239\n1,240\n1,241\n1,242\n1,243\n1,244\n1,245\n1,246\n1,247\n1,248\n1,249\n1,250\n1,251\n1,252\n1,253\n1,254\n1,255\n1,256\n1,257\n1,258\n1,259\n1,260\n1,261\n1,262\n1,263\n1,264\n1,265\n1,266\n1,267\n1,268\n1,269\n1,270\n1,271\n1,272\n1,273\n1,274\n1,275\n1,276\n1,277\n1,278\n1,279\n1,280\n1,281\n1,313\n1,314\n1,315\n1,316\n1,317\n1,318\n1,319\n1,320\n1,321\n1,322\n1,399\n1,851\n1,852\n1,853\n1,854\n1,855\n1,856\n1,857\n1,858\n1,859\n1,860\n1,861\n1,862\n1,863\n1,864\n1,865\n1,866\n1,867\n1,868\n1,869\n1,870\n1,871\n1,872\n1,873\n1,874\n1,875\n1,876\n1,583\n1,584\n1,585\n1,586\n1,587\n1,588\n1,589\n1,590\n1,591\n1,592\n1,593\n1,594\n1,595\n1,596\n1,388\n1,402\n1,407\n1,396\n1,877\n1,878\n1,879\n1,880\n1,881\n1,882\n1,883\n1,884\n1,885\n1,886\n1,887\n1,888\n1,889\n1,890\n1,975\n1,976\n1,977\n1,978\n1,979\n1,980\n1,981\n1,982\n1,983\n1,984\n1,985\n1,986\n1,987\n1,988\n1,390\n1,1057\n1,1058\n1,1059\n1,1060\n1,1061\n1,1062\n1,1063\n1,1064\n1,1065\n1,1066\n1,1067\n1,1068\n1,1069\n1,1070\n1,1071\n1,1072\n1,377\n1,395\n1,1087\n1,1088\n1,1089\n1,1090\n1,1091\n1,1092\n1,1093\n1,1094\n1,1095\n1,1096\n1,1097\n1,1098\n1,1099\n1,1100\n1,1101\n1,1105\n1,1106\n1,1107\n1,1108\n1,1109\n1,1110\n1,1111\n1,1112\n1,1113\n1,1114\n1,1115\n1,1116\n1,1117\n1,1118\n1,1119\n1,1120\n1,501\n1,502\n1,503\n1,504\n1,505\n1,506\n1,507\n1,508\n1,509\n1,510\n1,511\n1,512\n1,513\n1,514\n1,405\n1,378\n1,392\n1,403\n1,1506\n1,1507\n1,1508\n1,1509\n1,1510\n1,1511\n1,1512\n1,1513\n1,1514\n1,1515\n1,1516\n1,1517\n1,1518\n1,1519\n1,381\n1,1520\n1,1521\n1,1522\n1,1523\n1,1524\n1,1525\n1,1526\n1,1527\n1,1528\n1,1529\n1,1530\n1,1531\n1,400\n1,1686\n1,1687\n1,1688\n1,1689\n1,1690\n1,1691\n1,1692\n1,1693\n1,1694\n1,1695\n1,1696\n1,1697\n1,1698\n1,1699\n1,1700\n1,1701\n1,1671\n1,1672\n1,1673\n1,1674\n1,1675\n1,1676\n1,1677\n1,1678\n1,1679\n1,1680\n1,1681\n1,1682\n1,1683\n1,1684\n1,1685\n1,3356\n1,384\n1,1717\n1,1720\n1,1722\n1,1723\n1,1726\n1,1727\n1,1730\n1,1731\n1,1733\n1,1736\n1,1737\n1,1740\n1,1742\n1,1743\n1,1718\n1,1719\n1,1721\n1,1724\n1,1725\n1,1728\n1,1729\n1,1732\n1,1734\n1,1735\n1,1738\n1,1739\n1,1741\n1,1744\n1,374\n1,1755\n1,1762\n1,1763\n1,1756\n1,1764\n1,1757\n1,1758\n1,1765\n1,1766\n1,1759\n1,1760\n1,1767\n1,1761\n1,1768\n1,1769\n1,1770\n1,1771\n1,1772\n1,398\n1,1916\n1,1917\n1,1918\n1,1919\n1,1920\n1,1921\n1,1922\n1,1923\n1,1924\n1,1925\n1,1926\n1,1927\n1,1928\n1,1929\n1,1930\n1,1931\n1,1932\n1,1933\n1,1934\n1,1935\n1,1936\n1,1937\n1,1938\n1,1939\n1,1940\n1,1941\n1,375\n1,385\n1,383\n1,387\n1,2030\n1,2031\n1,2032\n1,2033\n1,2034\n1,2035\n1,2036\n1,2037\n1,2038\n1,2039\n1,2040\n1,2041\n1,2042\n1,2043\n1,393\n1,2044\n1,2045\n1,2046\n1,2047\n1,2048\n1,2049\n1,2050\n1,2051\n1,2052\n1,2053\n1,2054\n1,2055\n1,2056\n1,2057\n1,2058\n1,2059\n1,2060\n1,2061\n1,2062\n1,2063\n1,2064\n1,2065\n1,2066\n1,2067\n1,2068\n1,2069\n1,2070\n1,2071\n1,2072\n1,2073\n1,2074\n1,2075\n1,2076\n1,2077\n1,2078\n1,2079\n1,2080\n1,2081\n1,2082\n1,2083\n1,2084\n1,2085\n1,2086\n1,2087\n1,2088\n1,2089\n1,2090\n1,2091\n1,2092\n1,386\n1,401\n1,2751\n1,2752\n1,2753\n1,2754\n1,2755\n1,2756\n1,2757\n1,2758\n1,2759\n1,2760\n1,2761\n1,2762\n1,2763\n1,2764\n1,2765\n1,2766\n1,2767\n1,2768\n1,2769\n1,2770\n1,2771\n1,2772\n1,2773\n1,2774\n1,2775\n1,2776\n1,2777\n1,2778\n1,2779\n1,2780\n1,556\n1,557\n1,558\n1,559\n1,560\n1,561\n1,562\n1,563\n1,564\n1,565\n1,566\n1,567\n1,568\n1,569\n1,661\n1,662\n1,663\n1,664\n1,665\n1,666\n1,667\n1,668\n1,669\n1,670\n1,671\n1,672\n1,673\n1,674\n1,3117\n1,3118\n1,3119\n1,3120\n1,3121\n1,3122\n1,3123\n1,3124\n1,3125\n1,3126\n1,3127\n1,3128\n1,3129\n1,3130\n1,3131\n1,3146\n1,3147\n1,3148\n1,3149\n1,3150\n1,3151\n1,3152\n1,3153\n1,3154\n1,3155\n1,3156\n1,3157\n1,3158\n1,3159\n1,3160\n1,3161\n1,3162\n1,3163\n1,3164\n1,77\n1,78\n1,79\n1,80\n1,81\n1,82\n1,83\n1,84\n1,131\n1,132\n1,133\n1,134\n1,135\n1,136\n1,137\n1,138\n1,139\n1,140\n1,141\n1,142\n1,143\n1,144\n1,145\n1,146\n1,147\n1,148\n1,149\n1,150\n1,151\n1,152\n1,153\n1,154\n1,155\n1,156\n1,157\n1,158\n1,159\n1,160\n1,161\n1,162\n1,163\n1,164\n1,165\n1,183\n1,184\n1,185\n1,186\n1,187\n1,188\n1,189\n1,190\n1,191\n1,192\n1,193\n1,1121\n1,1122\n1,1123\n1,1124\n1,1125\n1,1126\n1,1127\n1,1128\n1,1129\n1,1130\n1,1131\n1,1132\n1,1174\n1,1175\n1,1176\n1,1177\n1,1178\n1,1179\n1,1180\n1,1181\n1,1182\n1,1183\n1,1184\n1,1185\n1,1186\n1,1187\n1,1289\n1,1290\n1,1291\n1,1292\n1,1293\n1,1294\n1,1295\n1,1296\n1,1297\n1,1298\n1,1299\n1,1325\n1,1326\n1,1327\n1,1328\n1,1329\n1,1330\n1,1331\n1,1332\n1,1333\n1,1334\n1,1391\n1,1388\n1,1394\n1,1387\n1,1392\n1,1389\n1,1390\n1,1335\n1,1336\n1,1337\n1,1338\n1,1339\n1,1340\n1,1341\n1,1342\n1,1343\n1,1344\n1,1345\n1,1346\n1,1347\n1,1348\n1,1349\n1,1350\n1,1351\n1,1212\n1,1213\n1,1214\n1,1215\n1,1216\n1,1217\n1,1218\n1,1219\n1,1220\n1,1221\n1,1222\n1,1223\n1,1224\n1,1225\n1,1226\n1,1227\n1,1228\n1,1229\n1,1230\n1,1231\n1,1232\n1,1233\n1,1234\n1,1352\n1,1353\n1,1354\n1,1355\n1,1356\n1,1357\n1,1358\n1,1359\n1,1360\n1,1361\n1,1364\n1,1371\n1,1372\n1,1373\n1,1374\n1,1375\n1,1376\n1,1377\n1,1378\n1,1379\n1,1380\n1,1381\n1,1382\n1,1386\n1,1383\n1,1385\n1,1384\n1,1546\n1,1547\n1,1548\n1,1549\n1,1550\n1,1551\n1,1552\n1,1553\n1,1554\n1,1555\n1,1556\n1,1557\n1,1558\n1,1559\n1,1560\n1,1561\n1,1893\n1,1894\n1,1895\n1,1896\n1,1897\n1,1898\n1,1899\n1,1900\n1,1901\n1,1801\n1,1802\n1,1803\n1,1804\n1,1805\n1,1806\n1,1807\n1,1808\n1,1809\n1,1810\n1,1811\n1,1812\n1,408\n1,409\n1,410\n1,411\n1,412\n1,413\n1,414\n1,415\n1,416\n1,417\n1,418\n1,1813\n1,1814\n1,1815\n1,1816\n1,1817\n1,1818\n1,1819\n1,1820\n1,1821\n1,1822\n1,1823\n1,1824\n1,1825\n1,1826\n1,1827\n1,1828\n1,1829\n1,1830\n1,1831\n1,1832\n1,1833\n1,1834\n1,1835\n1,1836\n1,1837\n1,1838\n1,1839\n1,1840\n1,1841\n1,1842\n1,1843\n1,1844\n1,1845\n1,1846\n1,1847\n1,1848\n1,1849\n1,1850\n1,1851\n1,1852\n1,1853\n1,1854\n1,1855\n1,1856\n1,1857\n1,1858\n1,1859\n1,1860\n1,1861\n1,1862\n1,1863\n1,1864\n1,1865\n1,1866\n1,1867\n1,1868\n1,1869\n1,1870\n1,1871\n1,1872\n1,1873\n1,1874\n1,1875\n1,1876\n1,1877\n1,1878\n1,1879\n1,1880\n1,1881\n1,1882\n1,1883\n1,1884\n1,1885\n1,1886\n1,1887\n1,1888\n1,1889\n1,1890\n1,1891\n1,1892\n1,1969\n1,1970\n1,1971\n1,1972\n1,1973\n1,1974\n1,1975\n1,1976\n1,1977\n1,1978\n1,1979\n1,1980\n1,1981\n1,1982\n1,1983\n1,1984\n1,1985\n1,1942\n1,1943\n1,1944\n1,1945\n1,1946\n1,1947\n1,1948\n1,1949\n1,1950\n1,1951\n1,1952\n1,1953\n1,1954\n1,1955\n1,1956\n1,2099\n1,2100\n1,2101\n1,2102\n1,2103\n1,2104\n1,2105\n1,2106\n1,2107\n1,2108\n1,2109\n1,2110\n1,2111\n1,2112\n1,2554\n1,2555\n1,2556\n1,2557\n1,2558\n1,2559\n1,2560\n1,2561\n1,2562\n1,2563\n1,2564\n1,3132\n1,3133\n1,3134\n1,3135\n1,3136\n1,3137\n1,3138\n1,3139\n1,3140\n1,3141\n1,3142\n1,3143\n1,3144\n1,3145\n1,3451\n1,3256\n1,3467\n1,3468\n1,3469\n1,3470\n1,3471\n1,3472\n1,3473\n1,3474\n1,3475\n1,3476\n1,3477\n1,3262\n1,3268\n1,3263\n1,3266\n1,3255\n1,3259\n1,3260\n1,3273\n1,3265\n1,3274\n1,3267\n1,3261\n1,3272\n1,3257\n1,3258\n1,3270\n1,3271\n1,3254\n1,3275\n1,3269\n1,3253\n1,323\n1,324\n1,325\n1,326\n1,327\n1,328\n1,329\n1,330\n1,331\n1,332\n1,333\n1,334\n1,335\n1,336\n1,3264\n1,3455\n1,3456\n1,3457\n1,3458\n1,3459\n1,3460\n1,3461\n1,3462\n1,3463\n1,3464\n1,3465\n1,3466\n1,1414\n1,1415\n1,1416\n1,1417\n1,1418\n1,1419\n1,1420\n1,1421\n1,1422\n1,1423\n1,1424\n1,1425\n1,1426\n1,1427\n1,1428\n1,1429\n1,1430\n1,1431\n1,1432\n1,1433\n1,1444\n1,1445\n1,1446\n1,1447\n1,1448\n1,1449\n1,1450\n1,1451\n1,1452\n1,1453\n1,1454\n1,1773\n1,1774\n1,1775\n1,1776\n1,1777\n1,1778\n1,1779\n1,1780\n1,1781\n1,1782\n1,1783\n1,1784\n1,1785\n1,1786\n1,1787\n1,1788\n1,1789\n1,1790\n1,282\n1,283\n1,284\n1,285\n1,286\n1,287\n1,288\n1,289\n1,290\n1,291\n1,292\n1,293\n1,294\n1,295\n1,296\n1,297\n1,298\n1,299\n1,300\n1,301\n1,302\n1,303\n1,304\n1,305\n1,306\n1,307\n1,308\n1,309\n1,310\n1,311\n1,312\n1,2216\n1,2217\n1,2218\n1,2219\n1,2220\n1,2221\n1,2222\n1,2223\n1,2224\n1,2225\n1,2226\n1,2227\n1,2228\n1,3038\n1,3039\n1,3040\n1,3041\n1,3042\n1,3043\n1,3044\n1,3045\n1,3046\n1,3047\n1,3048\n1,3049\n1,3050\n1,3051\n1,1\n1,6\n1,7\n1,8\n1,9\n1,10\n1,11\n1,12\n1,13\n1,14\n1,15\n1,16\n1,17\n1,18\n1,19\n1,20\n1,21\n1,22\n1,2\n1,3\n1,4\n1,5\n1,23\n1,24\n1,25\n1,26\n1,27\n1,28\n1,29\n1,30\n1,31\n1,32\n1,33\n1,34\n1,35\n1,36\n1,37\n1,38\n1,39\n1,40\n1,41\n1,42\n1,43\n1,44\n1,45\n1,46\n1,47\n1,48\n1,49\n1,50\n1,51\n1,52\n1,53\n1,54\n1,55\n1,56\n1,57\n1,58\n1,59\n1,60\n1,61\n1,62\n1,85\n1,86\n1,87\n1,88\n1,89\n1,90\n1,91\n1,92\n1,93\n1,94\n1,95\n1,96\n1,97\n1,98\n1,675\n1,676\n1,677\n1,678\n1,679\n1,680\n1,681\n1,682\n1,683\n1,684\n1,685\n1,686\n1,687\n1,688\n1,689\n1,690\n1,691\n1,692\n1,693\n1,694\n1,695\n1,696\n1,697\n1,698\n1,699\n1,700\n1,701\n1,702\n1,703\n1,704\n1,705\n1,706\n1,707\n1,708\n1,709\n1,710\n1,711\n1,712\n1,713\n1,714\n1,2609\n1,2610\n1,2611\n1,2612\n1,2613\n1,2614\n1,2615\n1,2616\n1,2617\n1,2618\n1,2619\n1,2620\n1,2621\n1,2622\n1,2623\n1,2624\n1,2625\n1,2626\n1,2627\n1,2628\n1,2629\n1,2630\n1,2631\n1,2632\n1,2633\n1,2634\n1,2635\n1,2636\n1,2637\n1,2638\n1,489\n1,490\n1,491\n1,492\n1,493\n1,494\n1,495\n1,496\n1,497\n1,498\n1,499\n1,500\n1,816\n1,817\n1,818\n1,819\n1,820\n1,821\n1,822\n1,823\n1,824\n1,825\n1,745\n1,746\n1,747\n1,748\n1,749\n1,750\n1,751\n1,752\n1,753\n1,754\n1,755\n1,756\n1,757\n1,758\n1,759\n1,760\n1,620\n1,621\n1,622\n1,623\n1,761\n1,762\n1,763\n1,764\n1,765\n1,766\n1,767\n1,768\n1,769\n1,770\n1,771\n1,772\n1,773\n1,774\n1,775\n1,776\n1,777\n1,778\n1,779\n1,780\n1,781\n1,782\n1,783\n1,784\n1,785\n1,543\n1,544\n1,545\n1,546\n1,547\n1,548\n1,549\n1,786\n1,787\n1,788\n1,789\n1,790\n1,791\n1,792\n1,793\n1,794\n1,795\n1,796\n1,797\n1,798\n1,799\n1,800\n1,801\n1,802\n1,803\n1,804\n1,805\n1,806\n1,807\n1,808\n1,809\n1,810\n1,811\n1,812\n1,813\n1,814\n1,815\n1,826\n1,827\n1,828\n1,829\n1,830\n1,831\n1,832\n1,833\n1,834\n1,835\n1,836\n1,837\n1,838\n1,839\n1,840\n1,841\n1,2639\n1,2640\n1,2641\n1,2642\n1,2643\n1,2644\n1,2645\n1,2646\n1,2647\n1,2648\n1,2649\n1,3225\n1,949\n1,950\n1,951\n1,952\n1,953\n1,954\n1,955\n1,956\n1,957\n1,958\n1,959\n1,960\n1,961\n1,962\n1,963\n1,1020\n1,1021\n1,1022\n1,1023\n1,1024\n1,1025\n1,1026\n1,1027\n1,1028\n1,1029\n1,1030\n1,1031\n1,1032\n1,989\n1,990\n1,991\n1,992\n1,993\n1,994\n1,995\n1,996\n1,997\n1,998\n1,999\n1,1000\n1,1001\n1,1002\n1,1003\n1,1004\n1,1005\n1,1006\n1,1007\n1,1008\n1,351\n1,352\n1,353\n1,354\n1,355\n1,356\n1,357\n1,358\n1,359\n1,1146\n1,1147\n1,1148\n1,1149\n1,1150\n1,1151\n1,1152\n1,1153\n1,1154\n1,1155\n1,1156\n1,1157\n1,1158\n1,1159\n1,1160\n1,1161\n1,1162\n1,1163\n1,1164\n1,1165\n1,1166\n1,1167\n1,1168\n1,1169\n1,1170\n1,1171\n1,1172\n1,1173\n1,1235\n1,1236\n1,1237\n1,1238\n1,1239\n1,1240\n1,1241\n1,1242\n1,1243\n1,1244\n1,1256\n1,1257\n1,1258\n1,1259\n1,1260\n1,1261\n1,1262\n1,1263\n1,1264\n1,1265\n1,1266\n1,1267\n1,1305\n1,1306\n1,1307\n1,1308\n1,1309\n1,1310\n1,1311\n1,1312\n1,1313\n1,1314\n1,1315\n1,1316\n1,1317\n1,1318\n1,1319\n1,1320\n1,1321\n1,1322\n1,1323\n1,1324\n1,1201\n1,1202\n1,1203\n1,1204\n1,1205\n1,1206\n1,1207\n1,1208\n1,1209\n1,1210\n1,1211\n1,1393\n1,1362\n1,1363\n1,1365\n1,1366\n1,1367\n1,1368\n1,1369\n1,1370\n1,1406\n1,1407\n1,1408\n1,1409\n1,1410\n1,1411\n1,1412\n1,1413\n1,1395\n1,1396\n1,1397\n1,1398\n1,1399\n1,1400\n1,1401\n1,1402\n1,1403\n1,1404\n1,1405\n1,1434\n1,1435\n1,1436\n1,1437\n1,1438\n1,1439\n1,1440\n1,1441\n1,1442\n1,1443\n1,1479\n1,1480\n1,1481\n1,1482\n1,1483\n1,1484\n1,1485\n1,1486\n1,1487\n1,1488\n1,1489\n1,1490\n1,1491\n1,1492\n1,1493\n1,1494\n1,1495\n1,1496\n1,1497\n1,1498\n1,1499\n1,1500\n1,1501\n1,1502\n1,1503\n1,1504\n1,1505\n1,436\n1,437\n1,438\n1,439\n1,440\n1,441\n1,442\n1,443\n1,444\n1,445\n1,446\n1,447\n1,448\n1,449\n1,450\n1,451\n1,452\n1,453\n1,454\n1,455\n1,1562\n1,1563\n1,1564\n1,1565\n1,1566\n1,1567\n1,1568\n1,1569\n1,1570\n1,1571\n1,1572\n1,1573\n1,1574\n1,1575\n1,1576\n1,337\n1,338\n1,339\n1,340\n1,341\n1,342\n1,343\n1,344\n1,345\n1,346\n1,347\n1,348\n1,349\n1,350\n1,1577\n1,1578\n1,1579\n1,1580\n1,1581\n1,1582\n1,1583\n1,1584\n1,1585\n1,1586\n1,1587\n1,1588\n1,1589\n1,1590\n1,1591\n1,1592\n1,1593\n1,1594\n1,1595\n1,1596\n1,1597\n1,1598\n1,1599\n1,1600\n1,1601\n1,1602\n1,1603\n1,1604\n1,1605\n1,1606\n1,1607\n1,1608\n1,1609\n1,1610\n1,1611\n1,1612\n1,1613\n1,1614\n1,1615\n1,1616\n1,1617\n1,1618\n1,1619\n1,1620\n1,1621\n1,1622\n1,1623\n1,1624\n1,1625\n1,1626\n1,1627\n1,1628\n1,1629\n1,1630\n1,1631\n1,1632\n1,1633\n1,1634\n1,1635\n1,1636\n1,1637\n1,1638\n1,1639\n1,1640\n1,1641\n1,1642\n1,1643\n1,1644\n1,1645\n1,550\n1,551\n1,552\n1,553\n1,554\n1,555\n1,1646\n1,1647\n1,1648\n1,1649\n1,1650\n1,1651\n1,1652\n1,1653\n1,1654\n1,1655\n1,1656\n1,1657\n1,1658\n1,1659\n1,1660\n1,1661\n1,1662\n1,1663\n1,1664\n1,1665\n1,1666\n1,1667\n1,1668\n1,1669\n1,1670\n1,1702\n1,1703\n1,1704\n1,1705\n1,1706\n1,1707\n1,1708\n1,1709\n1,1710\n1,1711\n1,1712\n1,1713\n1,1714\n1,1715\n1,1716\n1,1745\n1,1746\n1,1747\n1,1748\n1,1749\n1,1750\n1,1751\n1,1752\n1,1753\n1,1754\n1,1791\n1,1792\n1,1793\n1,1794\n1,1795\n1,1796\n1,1797\n1,1798\n1,1799\n1,1800\n1,1986\n1,1987\n1,1988\n1,1989\n1,1990\n1,1991\n1,1992\n1,1993\n1,1994\n1,1995\n1,1996\n1,1997\n1,1998\n1,1999\n1,2000\n1,2001\n1,2002\n1,2003\n1,2004\n1,2005\n1,2006\n1,2007\n1,2008\n1,2009\n1,2010\n1,2011\n1,2012\n1,2013\n1,2014\n1,2015\n1,2016\n1,2017\n1,2018\n1,2019\n1,2020\n1,2021\n1,2022\n1,2023\n1,2024\n1,2025\n1,2026\n1,2027\n1,2028\n1,2029\n1,2093\n1,2094\n1,2095\n1,2096\n1,2097\n1,2098\n1,3276\n1,3277\n1,3278\n1,3279\n1,3280\n1,3281\n1,3282\n1,3283\n1,3284\n1,3285\n1,3286\n1,3287\n1,2113\n1,2114\n1,2115\n1,2116\n1,2117\n1,2118\n1,2119\n1,2120\n1,2121\n1,2122\n1,2123\n1,2124\n1,2139\n1,2140\n1,2141\n1,2142\n1,2143\n1,2144\n1,2145\n1,2146\n1,2147\n1,2148\n1,2149\n1,2150\n1,2151\n1,2152\n1,2153\n1,2154\n1,2155\n1,2156\n1,2157\n1,2158\n1,2159\n1,2160\n1,2161\n1,2162\n1,2163\n1,2164\n1,2178\n1,2179\n1,2180\n1,2181\n1,2182\n1,2183\n1,2184\n1,2185\n1,2186\n1,2187\n1,2188\n1,2189\n1,2190\n1,2191\n1,2192\n1,2193\n1,2194\n1,2195\n1,2196\n1,2197\n1,2198\n1,2199\n1,2200\n1,2201\n1,2202\n1,2203\n1,2204\n1,2205\n1,2206\n1,2207\n1,2208\n1,2209\n1,2210\n1,2211\n1,2212\n1,2213\n1,2214\n1,2215\n1,2229\n1,2230\n1,2231\n1,2232\n1,2233\n1,2234\n1,2235\n1,2236\n1,2237\n1,2650\n1,2651\n1,2652\n1,2653\n1,2654\n1,2655\n1,2656\n1,2657\n1,2658\n1,2659\n1,2660\n1,2661\n1,2662\n1,2663\n1,3353\n1,3355\n1,2254\n1,2255\n1,2256\n1,2257\n1,2258\n1,2259\n1,2260\n1,2261\n1,2262\n1,2263\n1,2264\n1,2265\n1,2266\n1,2267\n1,2268\n1,2269\n1,2270\n1,419\n1,420\n1,421\n1,422\n1,423\n1,424\n1,425\n1,426\n1,427\n1,428\n1,429\n1,430\n1,431\n1,432\n1,433\n1,434\n1,435\n1,2271\n1,2272\n1,2273\n1,2274\n1,2275\n1,2276\n1,2277\n1,2278\n1,2279\n1,2280\n1,2281\n1,2296\n1,2297\n1,2298\n1,2299\n1,2300\n1,2301\n1,2302\n1,2303\n1,2304\n1,2305\n1,2306\n1,2307\n1,2308\n1,2309\n1,2344\n1,2345\n1,2346\n1,2347\n1,2348\n1,2349\n1,2350\n1,2351\n1,2352\n1,2353\n1,2354\n1,2355\n1,2356\n1,2357\n1,2375\n1,2376\n1,2377\n1,2378\n1,2379\n1,2380\n1,2381\n1,2382\n1,2383\n1,2384\n1,2385\n1,2386\n1,2387\n1,2388\n1,2389\n1,2390\n1,2391\n1,2392\n1,2393\n1,2394\n1,2395\n1,2396\n1,2397\n1,2398\n1,2399\n1,2400\n1,2401\n1,2402\n1,2403\n1,2404\n1,2405\n1,2664\n1,2665\n1,2666\n1,2667\n1,2668\n1,2669\n1,2670\n1,2671\n1,2672\n1,2673\n1,2674\n1,2675\n1,2676\n1,2677\n1,2678\n1,2679\n1,2680\n1,2681\n1,2682\n1,2683\n1,2684\n1,2685\n1,2686\n1,2687\n1,2688\n1,2689\n1,2690\n1,2691\n1,2692\n1,2693\n1,2694\n1,2695\n1,2696\n1,2697\n1,2698\n1,2699\n1,2700\n1,2701\n1,2702\n1,2703\n1,2704\n1,2406\n1,2407\n1,2408\n1,2409\n1,2410\n1,2411\n1,2412\n1,2413\n1,2414\n1,2415\n1,2416\n1,2417\n1,2418\n1,2419\n1,2420\n1,2421\n1,2422\n1,2423\n1,2424\n1,2425\n1,2426\n1,2427\n1,2428\n1,2429\n1,2430\n1,2431\n1,2432\n1,2433\n1,570\n1,573\n1,577\n1,580\n1,581\n1,571\n1,579\n1,582\n1,572\n1,575\n1,578\n1,574\n1,576\n1,3288\n1,3289\n1,3290\n1,3291\n1,3292\n1,3293\n1,3294\n1,3295\n1,3296\n1,3297\n1,3298\n1,3299\n1,2434\n1,2435\n1,2436\n1,2437\n1,2438\n1,2439\n1,2440\n1,2441\n1,2442\n1,2443\n1,2444\n1,2445\n1,2446\n1,2447\n1,2448\n1,2449\n1,2450\n1,2451\n1,2452\n1,2453\n1,2454\n1,2455\n1,2456\n1,2457\n1,2458\n1,2459\n1,2460\n1,2461\n1,2462\n1,2463\n1,2464\n1,2465\n1,2466\n1,2467\n1,2468\n1,2469\n1,2470\n1,2471\n1,2506\n1,2507\n1,2508\n1,2509\n1,2510\n1,2511\n1,2512\n1,2513\n1,2514\n1,2515\n1,2516\n1,2517\n1,2518\n1,2519\n1,2520\n1,2521\n1,2522\n1,2542\n1,2543\n1,2544\n1,2545\n1,2546\n1,2547\n1,2548\n1,2549\n1,2550\n1,2551\n1,2552\n1,2553\n1,2565\n1,2566\n1,2567\n1,2568\n1,2569\n1,2570\n1,2571\n1,2926\n1,2927\n1,2928\n1,2929\n1,2930\n1,2931\n1,2932\n1,2933\n1,2934\n1,2935\n1,2936\n1,2937\n1,2938\n1,2939\n1,2940\n1,2941\n1,2942\n1,2943\n1,2944\n1,2945\n1,2946\n1,2947\n1,2948\n1,2949\n1,2950\n1,2951\n1,2952\n1,2953\n1,2954\n1,2955\n1,2956\n1,2957\n1,2958\n1,2959\n1,2960\n1,2961\n1,2962\n1,2963\n1,3004\n1,3005\n1,3006\n1,3007\n1,3008\n1,3009\n1,3010\n1,3011\n1,3012\n1,3013\n1,3014\n1,3015\n1,3016\n1,3017\n1,2964\n1,2965\n1,2966\n1,2967\n1,2968\n1,2969\n1,2970\n1,2971\n1,2972\n1,2973\n1,2974\n1,2975\n1,2976\n1,2977\n1,2978\n1,2979\n1,2980\n1,2981\n1,2982\n1,2983\n1,2984\n1,2985\n1,2986\n1,2987\n1,2988\n1,2989\n1,2990\n1,2991\n1,2992\n1,2993\n1,2994\n1,2995\n1,2996\n1,2997\n1,2998\n1,2999\n1,3000\n1,3001\n1,3002\n1,3003\n1,3018\n1,3019\n1,3020\n1,3021\n1,3022\n1,3023\n1,3024\n1,3025\n1,3026\n1,3027\n1,3028\n1,3029\n1,3030\n1,3031\n1,3032\n1,3033\n1,3034\n1,3035\n1,3036\n1,3037\n1,3064\n1,3065\n1,3066\n1,3067\n1,3068\n1,3069\n1,3070\n1,3071\n1,3072\n1,3073\n1,3074\n1,3075\n1,3076\n1,3077\n1,3078\n1,3079\n1,3080\n1,3052\n1,3053\n1,3054\n1,3055\n1,3056\n1,3057\n1,3058\n1,3059\n1,3060\n1,3061\n1,3062\n1,3063\n1,3081\n1,3082\n1,3083\n1,3084\n1,3085\n1,3086\n1,3087\n1,3088\n1,3089\n1,3090\n1,3091\n1,3092\n1,3093\n1,3094\n1,3095\n1,3096\n1,3097\n1,3098\n1,3099\n1,3100\n1,3101\n1,3102\n1,3103\n1,3104\n1,3105\n1,3106\n1,3107\n1,3108\n1,3109\n1,3110\n1,3111\n1,3112\n1,3113\n1,3114\n1,3115\n1,3116\n1,2731\n1,2732\n1,2733\n1,2734\n1,2735\n1,2736\n1,2737\n1,2738\n1,2739\n1,2740\n1,2741\n1,2742\n1,2743\n1,2744\n1,2745\n1,2746\n1,2747\n1,2748\n1,2749\n1,2750\n1,111\n1,112\n1,113\n1,114\n1,115\n1,116\n1,117\n1,118\n1,119\n1,120\n1,121\n1,122\n1,1073\n1,1074\n1,1075\n1,1076\n1,1077\n1,1078\n1,1079\n1,1080\n1,1081\n1,1082\n1,1083\n1,1084\n1,1085\n1,1086\n1,2125\n1,2126\n1,2127\n1,2128\n1,2129\n1,2130\n1,2131\n1,2132\n1,2133\n1,2134\n1,2135\n1,2136\n1,2137\n1,2138\n1,3503\n1,360\n1,361\n1,362\n1,363\n1,364\n1,365\n1,366\n1,367\n1,368\n1,369\n1,370\n1,371\n1,372\n1,373\n1,3354\n1,3351\n1,1532\n1,1533\n1,1534\n1,1535\n1,1536\n1,1537\n1,1538\n1,1539\n1,1540\n1,1541\n1,1542\n1,1543\n1,1544\n1,1545\n1,1957\n1,1958\n1,1959\n1,1960\n1,1961\n1,1962\n1,1963\n1,1964\n1,1965\n1,1966\n1,1967\n1,1968\n3,3250\n3,2819\n3,2820\n3,2821\n3,2822\n3,2823\n3,2824\n3,2825\n3,2826\n3,2827\n3,2828\n3,2829\n3,2830\n3,2831\n3,2832\n3,2833\n3,2834\n3,2835\n3,2836\n3,2837\n3,2838\n3,3226\n3,3227\n3,3228\n3,3229\n3,3230\n3,3231\n3,3232\n3,3233\n3,3234\n3,3235\n3,3236\n3,3237\n3,3238\n3,3239\n3,3240\n3,3241\n3,3242\n3,3243\n3,3244\n3,3245\n3,3246\n3,3247\n3,3248\n3,3249\n3,2839\n3,2840\n3,2841\n3,2842\n3,2843\n3,2844\n3,2845\n3,2846\n3,2847\n3,2848\n3,2849\n3,2850\n3,2851\n3,2852\n3,2853\n3,2854\n3,2855\n3,2856\n3,3166\n3,3167\n3,3168\n3,3171\n3,3223\n3,2858\n3,2861\n3,2865\n3,2868\n3,2871\n3,2873\n3,2877\n3,2880\n3,2883\n3,2885\n3,2888\n3,2893\n3,2894\n3,2898\n3,2901\n3,2904\n3,2906\n3,2911\n3,2913\n3,2915\n3,2917\n3,2919\n3,2921\n3,2923\n3,2925\n3,2859\n3,2860\n3,2864\n3,2867\n3,2869\n3,2872\n3,2878\n3,2879\n3,2884\n3,2887\n3,2889\n3,2892\n3,2896\n3,2897\n3,2902\n3,2905\n3,2907\n3,2910\n3,2914\n3,2916\n3,2918\n3,2920\n3,2922\n3,2924\n3,2857\n3,2862\n3,2863\n3,2866\n3,2870\n3,2874\n3,2875\n3,2876\n3,2881\n3,2882\n3,2886\n3,2890\n3,2891\n3,2895\n3,2899\n3,2900\n3,2903\n3,2908\n3,2909\n3,2912\n3,3165\n3,3169\n3,3170\n3,3252\n3,3224\n3,3251\n3,3340\n3,3339\n3,3338\n3,3337\n3,3341\n3,3345\n3,3342\n3,3346\n3,3343\n3,3347\n3,3344\n3,3348\n3,3360\n3,3361\n3,3362\n3,3363\n3,3364\n3,3172\n3,3173\n3,3174\n3,3175\n3,3176\n3,3177\n3,3178\n3,3179\n3,3180\n3,3181\n3,3182\n3,3183\n3,3184\n3,3185\n3,3186\n3,3187\n3,3188\n3,3189\n3,3190\n3,3191\n3,3192\n3,3193\n3,3194\n3,3195\n3,3196\n3,3197\n3,3198\n3,3199\n3,3200\n3,3201\n3,3202\n3,3203\n3,3204\n3,3205\n3,3206\n3,3428\n3,3207\n3,3208\n3,3209\n3,3210\n3,3211\n3,3212\n3,3429\n3,3213\n3,3214\n3,3215\n3,3216\n3,3217\n3,3218\n3,3219\n3,3220\n3,3221\n3,3222\n5,51\n5,52\n5,53\n5,54\n5,55\n5,56\n5,57\n5,58\n5,59\n5,60\n5,61\n5,62\n5,798\n5,799\n5,800\n5,801\n5,802\n5,803\n5,804\n5,805\n5,806\n5,3225\n5,1325\n5,1326\n5,1327\n5,1328\n5,1329\n5,1330\n5,1331\n5,1332\n5,1333\n5,1334\n5,1557\n5,2506\n5,2591\n5,2592\n5,2593\n5,2594\n5,2595\n5,2596\n5,2597\n5,2598\n5,2599\n5,2600\n5,2601\n5,2602\n5,2603\n5,2604\n5,2605\n5,2606\n5,2607\n5,2608\n5,2627\n5,2631\n5,2638\n5,1158\n5,1159\n5,1160\n5,1161\n5,1162\n5,1163\n5,1164\n5,1165\n5,1166\n5,1167\n5,1168\n5,1169\n5,1170\n5,1171\n5,1172\n5,1173\n5,1174\n5,1175\n5,1176\n5,1177\n5,1178\n5,1179\n5,1180\n5,1181\n5,1182\n5,1183\n5,1184\n5,1185\n5,1186\n5,1187\n5,1414\n5,1415\n5,1416\n5,1417\n5,1418\n5,1419\n5,1420\n5,1421\n5,1422\n5,1423\n5,1424\n5,1425\n5,1426\n5,1427\n5,1428\n5,1429\n5,1430\n5,1431\n5,1432\n5,1433\n5,1801\n5,1802\n5,1803\n5,1804\n5,1805\n5,1806\n5,1807\n5,1808\n5,1809\n5,1810\n5,1811\n5,1812\n5,2003\n5,2004\n5,2005\n5,2006\n5,2007\n5,2008\n5,2009\n5,2010\n5,2011\n5,2012\n5,2013\n5,2014\n5,2193\n5,2194\n5,2195\n5,2196\n5,2197\n5,2198\n5,2199\n5,2200\n5,2201\n5,2202\n5,2203\n5,424\n5,428\n5,430\n5,434\n5,2310\n5,2311\n5,2312\n5,2313\n5,2314\n5,2315\n5,2316\n5,2317\n5,2282\n5,2283\n5,2284\n5,2358\n5,2359\n5,2360\n5,2361\n5,2362\n5,2363\n5,2364\n5,2365\n5,2366\n5,2367\n5,2368\n5,2369\n5,2370\n5,2371\n5,2372\n5,2373\n5,2374\n5,2420\n5,2421\n5,2422\n5,2423\n5,2424\n5,2425\n5,2426\n5,2427\n5,2488\n5,2489\n5,2511\n5,2512\n5,2513\n5,2711\n5,2715\n5,3365\n5,3366\n5,3367\n5,3368\n5,3369\n5,3370\n5,3371\n5,3372\n5,3373\n5,3374\n5,2926\n5,2927\n5,2928\n5,2929\n5,2930\n5,2931\n5,2932\n5,2933\n5,2934\n5,2935\n5,2936\n5,2937\n5,3075\n5,3076\n5,166\n5,167\n5,168\n5,169\n5,170\n5,171\n5,172\n5,173\n5,174\n5,175\n5,176\n5,177\n5,178\n5,179\n5,180\n5,181\n5,182\n5,3426\n5,2625\n5,816\n5,817\n5,818\n5,819\n5,820\n5,821\n5,822\n5,823\n5,824\n5,825\n5,768\n5,769\n5,770\n5,771\n5,772\n5,773\n5,774\n5,775\n5,776\n5,777\n5,778\n5,909\n5,910\n5,911\n5,912\n5,913\n5,914\n5,915\n5,916\n5,917\n5,918\n5,919\n5,920\n5,921\n5,922\n5,935\n5,936\n5,937\n5,938\n5,939\n5,940\n5,941\n5,942\n5,943\n5,944\n5,945\n5,946\n5,947\n5,948\n5,3301\n5,3300\n5,3302\n5,3303\n5,3304\n5,3305\n5,3306\n5,3307\n5,3308\n5,3309\n5,3310\n5,3311\n5,3312\n5,3313\n5,3314\n5,3315\n5,3316\n5,3317\n5,3318\n5,1256\n5,1257\n5,1258\n5,1259\n5,1260\n5,1261\n5,1262\n5,1263\n5,1264\n5,1265\n5,1266\n5,1267\n5,2490\n5,2542\n5,2543\n5,2544\n5,2545\n5,2546\n5,2547\n5,2548\n5,2549\n5,2550\n5,2551\n5,2552\n5,2553\n5,3411\n5,3403\n5,3423\n5,1212\n5,1213\n5,1214\n5,1215\n5,1216\n5,1217\n5,1218\n5,1219\n5,1220\n5,1221\n5,1222\n5,1223\n5,1224\n5,1225\n5,1226\n5,1227\n5,1228\n5,1229\n5,1230\n5,1231\n5,1232\n5,1233\n5,1234\n5,1434\n5,1435\n5,1436\n5,1437\n5,1438\n5,1439\n5,1440\n5,1441\n5,1442\n5,1443\n5,2204\n5,2205\n5,2206\n5,2207\n5,2208\n5,2209\n5,2210\n5,2211\n5,2212\n5,2213\n5,2214\n5,2215\n5,3404\n5,2491\n5,2492\n5,2493\n5,3028\n5,3029\n5,3030\n5,3031\n5,3032\n5,3033\n5,3034\n5,3035\n5,3036\n5,3037\n5,23\n5,24\n5,25\n5,26\n5,27\n5,28\n5,29\n5,30\n5,31\n5,32\n5,33\n5,34\n5,35\n5,36\n5,37\n5,111\n5,112\n5,113\n5,114\n5,115\n5,116\n5,117\n5,118\n5,119\n5,120\n5,121\n5,122\n5,515\n5,516\n5,517\n5,518\n5,519\n5,520\n5,521\n5,522\n5,523\n5,524\n5,525\n5,526\n5,527\n5,528\n5,269\n5,270\n5,271\n5,272\n5,273\n5,274\n5,275\n5,276\n5,277\n5,278\n5,279\n5,280\n5,281\n5,891\n5,892\n5,893\n5,894\n5,895\n5,896\n5,897\n5,898\n5,899\n5,900\n5,901\n5,902\n5,903\n5,904\n5,905\n5,906\n5,907\n5,908\n5,1105\n5,1106\n5,1107\n5,1108\n5,1109\n5,1110\n5,1111\n5,1112\n5,1113\n5,1114\n5,1115\n5,1116\n5,1117\n5,1118\n5,1119\n5,1120\n5,470\n5,471\n5,472\n5,473\n5,474\n5,3424\n5,2690\n5,2691\n5,2692\n5,2693\n5,2694\n5,2695\n5,2696\n5,2697\n5,2698\n5,2699\n5,2700\n5,2701\n5,2702\n5,2703\n5,2704\n5,2494\n5,2514\n5,2515\n5,2516\n5,2517\n5,3132\n5,3133\n5,3134\n5,3135\n5,3136\n5,3137\n5,3138\n5,3139\n5,3140\n5,3141\n5,3142\n5,3143\n5,3144\n5,3145\n5,3408\n5,3\n5,4\n5,5\n5,38\n5,39\n5,40\n5,41\n5,42\n5,43\n5,44\n5,45\n5,46\n5,47\n5,48\n5,49\n5,50\n5,826\n5,827\n5,828\n5,829\n5,830\n5,831\n5,832\n5,833\n5,834\n5,835\n5,836\n5,837\n5,838\n5,839\n5,840\n5,841\n5,949\n5,950\n5,951\n5,952\n5,953\n5,954\n5,955\n5,956\n5,957\n5,958\n5,959\n5,960\n5,961\n5,962\n5,963\n5,475\n5,476\n5,477\n5,478\n5,479\n5,480\n5,3354\n5,3351\n5,1395\n5,1396\n5,1397\n5,1398\n5,1399\n5,1400\n5,1401\n5,1402\n5,1403\n5,1404\n5,1405\n5,1455\n5,1456\n5,1457\n5,1458\n5,1459\n5,1460\n5,1461\n5,1462\n5,1463\n5,1464\n5,1465\n5,1520\n5,1521\n5,1522\n5,1523\n5,1524\n5,1525\n5,1526\n5,1527\n5,1528\n5,1529\n5,1530\n5,1531\n5,3276\n5,3277\n5,3278\n5,3279\n5,3280\n5,3281\n5,3282\n5,3283\n5,3284\n5,3285\n5,3286\n5,3287\n5,2125\n5,2126\n5,2127\n5,2128\n5,2129\n5,2130\n5,2131\n5,2132\n5,2133\n5,2134\n5,2135\n5,2136\n5,2137\n5,2138\n5,3410\n5,2476\n5,2484\n5,2495\n5,2496\n5,2497\n5,2498\n5,2709\n5,2710\n5,2712\n5,3038\n5,3039\n5,3040\n5,3041\n5,3042\n5,3043\n5,3044\n5,3045\n5,3046\n5,3047\n5,3048\n5,3049\n5,3050\n5,3051\n5,3077\n5,77\n5,78\n5,79\n5,80\n5,81\n5,82\n5,83\n5,84\n5,3421\n5,246\n5,247\n5,248\n5,249\n5,250\n5,251\n5,252\n5,253\n5,254\n5,255\n5,256\n5,257\n5,258\n5,259\n5,260\n5,261\n5,262\n5,263\n5,264\n5,265\n5,266\n5,267\n5,268\n5,786\n5,787\n5,788\n5,789\n5,790\n5,791\n5,792\n5,793\n5,794\n5,795\n5,796\n5,797\n5,1562\n5,1563\n5,1564\n5,1565\n5,1566\n5,1567\n5,1568\n5,1569\n5,1570\n5,1571\n5,1572\n5,1573\n5,1574\n5,1575\n5,1576\n5,1839\n5,1840\n5,1841\n5,1842\n5,1843\n5,1844\n5,1845\n5,1846\n5,1847\n5,1848\n5,1849\n5,1850\n5,1851\n5,1852\n5,1986\n5,1987\n5,1988\n5,1989\n5,1990\n5,1991\n5,1992\n5,1993\n5,1994\n5,1995\n5,1996\n5,1997\n5,1998\n5,1999\n5,2000\n5,2001\n5,2002\n5,3415\n5,2650\n5,2651\n5,2652\n5,2653\n5,2654\n5,2655\n5,2656\n5,2657\n5,2658\n5,2659\n5,2660\n5,2661\n5,2662\n5,2663\n5,2296\n5,2297\n5,2298\n5,2299\n5,2300\n5,2301\n5,2302\n5,2303\n5,2304\n5,2305\n5,2306\n5,2307\n5,2308\n5,2309\n5,2334\n5,2335\n5,2336\n5,2337\n5,2338\n5,2339\n5,2340\n5,2341\n5,2342\n5,2343\n5,2434\n5,2435\n5,2436\n5,2437\n5,2438\n5,2439\n5,2440\n5,2441\n5,2442\n5,2443\n5,2444\n5,2445\n5,2446\n5,2447\n5,2448\n5,2461\n5,2462\n5,2463\n5,2464\n5,2465\n5,2466\n5,2467\n5,2468\n5,2469\n5,2470\n5,2471\n5,2478\n5,2518\n5,2519\n5,2520\n5,2521\n5,2522\n5,456\n5,457\n5,458\n5,459\n5,460\n5,461\n5,462\n5,463\n5,464\n5,465\n5,466\n5,467\n5,3078\n5,3079\n5,3080\n5,3416\n5,923\n5,924\n5,925\n5,926\n5,927\n5,928\n5,929\n5,930\n5,931\n5,932\n5,933\n5,934\n5,1020\n5,1021\n5,1022\n5,1023\n5,1024\n5,1025\n5,1026\n5,1027\n5,1028\n5,1029\n5,1030\n5,1031\n5,1032\n5,481\n5,482\n5,483\n5,484\n5,1188\n5,1189\n5,1190\n5,1191\n5,1192\n5,1193\n5,1194\n5,1195\n5,1196\n5,1197\n5,1198\n5,1199\n5,1200\n5,436\n5,437\n5,438\n5,439\n5,440\n5,441\n5,442\n5,443\n5,444\n5,445\n5,446\n5,447\n5,448\n5,449\n5,450\n5,451\n5,453\n5,454\n5,455\n5,337\n5,338\n5,339\n5,340\n5,341\n5,342\n5,343\n5,344\n5,345\n5,346\n5,347\n5,348\n5,349\n5,350\n5,1577\n5,1578\n5,1579\n5,1580\n5,1581\n5,1582\n5,1583\n5,1584\n5,1585\n5,1586\n5,1861\n5,1862\n5,1863\n5,1864\n5,1865\n5,1866\n5,1867\n5,1868\n5,1869\n5,1870\n5,1871\n5,1872\n5,1873\n5,3359\n5,2406\n5,2407\n5,2408\n5,2409\n5,2410\n5,2411\n5,2412\n5,2413\n5,2414\n5,2415\n5,2416\n5,2417\n5,2418\n5,2419\n5,2499\n5,2706\n5,2708\n5,2713\n5,2716\n5,2720\n5,2721\n5,2722\n5,2723\n5,2724\n5,2725\n5,2726\n5,2727\n5,2728\n5,2729\n5,2730\n5,2565\n5,2566\n5,2567\n5,2568\n5,2569\n5,2570\n5,2571\n5,2781\n5,2782\n5,2783\n5,2784\n5,2785\n5,2786\n5,2787\n5,2788\n5,2789\n5,2790\n5,2791\n5,2792\n5,2793\n5,2794\n5,2795\n5,2796\n5,2797\n5,2798\n5,2799\n5,2800\n5,2801\n5,2802\n5,2975\n5,2976\n5,2977\n5,2978\n5,2979\n5,2980\n5,2981\n5,2982\n5,2983\n5,2984\n5,2985\n5,2986\n5,183\n5,184\n5,185\n5,186\n5,187\n5,188\n5,189\n5,190\n5,191\n5,192\n5,193\n5,205\n5,206\n5,207\n5,208\n5,209\n5,210\n5,211\n5,212\n5,213\n5,214\n5,215\n5,216\n5,217\n5,218\n5,219\n5,220\n5,221\n5,222\n5,3417\n5,583\n5,584\n5,585\n5,586\n5,587\n5,588\n5,589\n5,590\n5,591\n5,592\n5,593\n5,594\n5,595\n5,596\n5,976\n5,977\n5,978\n5,979\n5,984\n5,1087\n5,1088\n5,1089\n5,1090\n5,1091\n5,1092\n5,1093\n5,1094\n5,1095\n5,1096\n5,1097\n5,1098\n5,1099\n5,1100\n5,1101\n5,1305\n5,1306\n5,1307\n5,1308\n5,1309\n5,1310\n5,1311\n5,1312\n5,1313\n5,1314\n5,1315\n5,1316\n5,1317\n5,1318\n5,1319\n5,1320\n5,1321\n5,1322\n5,1323\n5,1324\n5,1406\n5,1407\n5,1408\n5,1409\n5,1410\n5,1411\n5,1412\n5,1413\n5,1686\n5,1687\n5,1688\n5,1689\n5,1690\n5,1691\n5,1692\n5,1693\n5,1694\n5,1695\n5,1696\n5,1697\n5,1698\n5,1699\n5,1700\n5,1701\n5,408\n5,409\n5,410\n5,411\n5,412\n5,413\n5,414\n5,415\n5,416\n5,417\n5,418\n5,1813\n5,1814\n5,1815\n5,1816\n5,1817\n5,1818\n5,1819\n5,1820\n5,1821\n5,1822\n5,1823\n5,1824\n5,1825\n5,1826\n5,1827\n5,1828\n5,1969\n5,1970\n5,1971\n5,1972\n5,1973\n5,1974\n5,1975\n5,1976\n5,1977\n5,1978\n5,1979\n5,1980\n5,1981\n5,1982\n5,1983\n5,1984\n5,1985\n5,2113\n5,2114\n5,2115\n5,2116\n5,2117\n5,2118\n5,2119\n5,2120\n5,2121\n5,2122\n5,2123\n5,2124\n5,2149\n5,2150\n5,2151\n5,2152\n5,2153\n5,2154\n5,2155\n5,2156\n5,2157\n5,2158\n5,2159\n5,2160\n5,2161\n5,2162\n5,2163\n5,2164\n5,2676\n5,2677\n5,2678\n5,2679\n5,2680\n5,2681\n5,2682\n5,2683\n5,2684\n5,2685\n5,2686\n5,2687\n5,2688\n5,2689\n5,3418\n5,2500\n5,2501\n5,2803\n5,2804\n5,2805\n5,2806\n5,2807\n5,2808\n5,2809\n5,2810\n5,2811\n5,2812\n5,2813\n5,2814\n5,2815\n5,2816\n5,2817\n5,2818\n5,2949\n5,2950\n5,2951\n5,2952\n5,2953\n5,2954\n5,2955\n5,2956\n5,2957\n5,2958\n5,2959\n5,2960\n5,2961\n5,2962\n5,2963\n5,3004\n5,3005\n5,3006\n5,3007\n5,3008\n5,3009\n5,3010\n5,3011\n5,3012\n5,3013\n5,3014\n5,3015\n5,3016\n5,3017\n5,3092\n5,3093\n5,3094\n5,3095\n5,3096\n5,3097\n5,3098\n5,3099\n5,3100\n5,3101\n5,3102\n5,3103\n5,3409\n5,299\n5,300\n5,301\n5,302\n5,303\n5,304\n5,305\n5,306\n5,307\n5,308\n5,309\n5,310\n5,311\n5,312\n5,851\n5,852\n5,853\n5,854\n5,855\n5,856\n5,857\n5,858\n5,859\n5,860\n5,861\n5,862\n5,863\n5,864\n5,865\n5,866\n5,867\n5,868\n5,869\n5,870\n5,871\n5,872\n5,873\n5,874\n5,875\n5,876\n5,1057\n5,1058\n5,1059\n5,1060\n5,1061\n5,1062\n5,1063\n5,1064\n5,1065\n5,1066\n5,1067\n5,1068\n5,1069\n5,1070\n5,1071\n5,1072\n5,501\n5,502\n5,503\n5,504\n5,505\n5,506\n5,507\n5,508\n5,509\n5,510\n5,511\n5,512\n5,513\n5,514\n5,1444\n5,1445\n5,1446\n5,1447\n5,1448\n5,1449\n5,1450\n5,1451\n5,1452\n5,1453\n5,1454\n5,1496\n5,1497\n5,1498\n5,1499\n5,1500\n5,1501\n5,1502\n5,1503\n5,1504\n5,1505\n5,1671\n5,1672\n5,1673\n5,1674\n5,1675\n5,1676\n5,1677\n5,1678\n5,1679\n5,1680\n5,1681\n5,1682\n5,1683\n5,1684\n5,1685\n5,2044\n5,2045\n5,2046\n5,2047\n5,2048\n5,2049\n5,2050\n5,2051\n5,2052\n5,2053\n5,2054\n5,2055\n5,2056\n5,2057\n5,2058\n5,2059\n5,2060\n5,2061\n5,2062\n5,2063\n5,2064\n5,2238\n5,2239\n5,2240\n5,2241\n5,2242\n5,2243\n5,2244\n5,2245\n5,2246\n5,2247\n5,2248\n5,2249\n5,2250\n5,2251\n5,2252\n5,2253\n5,2391\n5,2392\n5,2393\n5,2394\n5,2395\n5,2396\n5,2397\n5,2398\n5,2399\n5,2400\n5,2401\n5,2402\n5,2403\n5,2404\n5,2405\n5,570\n5,573\n5,577\n5,580\n5,581\n5,571\n5,579\n5,582\n5,572\n5,575\n5,578\n5,574\n5,576\n5,2707\n5,2714\n5,2717\n5,2718\n5,3146\n5,3147\n5,3148\n5,3149\n5,3150\n5,3151\n5,3152\n5,3153\n5,3154\n5,3155\n5,3156\n5,3157\n5,3158\n5,3159\n5,3160\n5,3161\n5,3162\n5,3163\n5,3164\n5,3438\n5,3442\n5,3436\n5,3454\n5,3432\n5,3447\n5,3434\n5,3449\n5,3445\n5,3439\n5,3435\n5,3448\n5,3437\n5,3446\n5,3444\n5,3451\n5,3430\n5,3482\n5,3485\n5,3499\n5,3490\n5,3489\n5,3492\n5,3493\n5,3498\n5,3481\n5,3503\n8,3427\n8,3357\n8,1\n8,6\n8,7\n8,8\n8,9\n8,10\n8,11\n8,12\n8,13\n8,14\n8,15\n8,16\n8,17\n8,18\n8,19\n8,20\n8,21\n8,22\n8,3411\n8,3412\n8,3419\n8,2\n8,3\n8,4\n8,5\n8,23\n8,24\n8,25\n8,26\n8,27\n8,28\n8,29\n8,30\n8,31\n8,32\n8,33\n8,34\n8,35\n8,36\n8,37\n8,3256\n8,3350\n8,3349\n8,38\n8,39\n8,40\n8,41\n8,42\n8,43\n8,44\n8,45\n8,46\n8,47\n8,48\n8,49\n8,50\n8,3403\n8,51\n8,52\n8,53\n8,54\n8,55\n8,56\n8,57\n8,58\n8,59\n8,60\n8,61\n8,62\n8,3406\n8,379\n8,391\n8,63\n8,64\n8,65\n8,66\n8,67\n8,68\n8,69\n8,70\n8,71\n8,72\n8,73\n8,74\n8,75\n8,76\n8,77\n8,78\n8,79\n8,80\n8,81\n8,82\n8,83\n8,84\n8,85\n8,86\n8,87\n8,88\n8,89\n8,90\n8,91\n8,92\n8,93\n8,94\n8,95\n8,96\n8,97\n8,98\n8,99\n8,100\n8,101\n8,102\n8,103\n8,104\n8,105\n8,106\n8,107\n8,108\n8,109\n8,110\n8,3402\n8,3389\n8,3390\n8,3391\n8,3392\n8,3393\n8,3394\n8,3395\n8,3396\n8,3397\n8,3398\n8,3399\n8,3400\n8,3401\n8,3262\n8,376\n8,397\n8,382\n8,111\n8,112\n8,113\n8,114\n8,115\n8,116\n8,117\n8,118\n8,119\n8,120\n8,121\n8,122\n8,389\n8,404\n8,406\n8,3421\n8,380\n8,394\n8,3268\n8,3413\n8,3263\n8,123\n8,124\n8,125\n8,126\n8,127\n8,128\n8,129\n8,130\n8,2572\n8,2573\n8,2574\n8,2575\n8,2576\n8,2577\n8,2578\n8,2579\n8,2580\n8,2581\n8,2582\n8,2583\n8,2584\n8,2585\n8,2586\n8,2587\n8,2588\n8,2589\n8,2590\n8,3266\n8,131\n8,132\n8,133\n8,134\n8,135\n8,136\n8,137\n8,138\n8,139\n8,140\n8,141\n8,142\n8,143\n8,144\n8,145\n8,146\n8,147\n8,148\n8,149\n8,150\n8,151\n8,152\n8,153\n8,154\n8,155\n8,156\n8,157\n8,158\n8,159\n8,160\n8,161\n8,162\n8,163\n8,164\n8,165\n8,166\n8,167\n8,168\n8,169\n8,170\n8,171\n8,172\n8,173\n8,174\n8,175\n8,176\n8,177\n8,178\n8,179\n8,180\n8,181\n8,182\n8,3426\n8,3416\n8,183\n8,184\n8,185\n8,186\n8,187\n8,188\n8,189\n8,190\n8,191\n8,192\n8,193\n8,194\n8,195\n8,196\n8,197\n8,198\n8,199\n8,200\n8,201\n8,202\n8,203\n8,204\n8,515\n8,516\n8,517\n8,518\n8,519\n8,520\n8,521\n8,522\n8,523\n8,524\n8,525\n8,526\n8,527\n8,528\n8,205\n8,206\n8,207\n8,208\n8,209\n8,210\n8,211\n8,212\n8,213\n8,214\n8,215\n8,216\n8,217\n8,218\n8,219\n8,220\n8,221\n8,222\n8,223\n8,224\n8,225\n8,3336\n8,715\n8,716\n8,717\n8,718\n8,719\n8,720\n8,721\n8,722\n8,723\n8,724\n8,725\n8,726\n8,727\n8,728\n8,729\n8,730\n8,731\n8,732\n8,733\n8,734\n8,735\n8,736\n8,737\n8,738\n8,739\n8,740\n8,741\n8,742\n8,743\n8,744\n8,3324\n8,3417\n8,226\n8,227\n8,228\n8,229\n8,230\n8,231\n8,232\n8,233\n8,234\n8,235\n8,236\n8,237\n8,238\n8,239\n8,240\n8,241\n8,242\n8,243\n8,244\n8,245\n8,246\n8,247\n8,248\n8,249\n8,250\n8,251\n8,252\n8,253\n8,254\n8,255\n8,256\n8,257\n8,258\n8,259\n8,260\n8,261\n8,262\n8,263\n8,264\n8,265\n8,266\n8,267\n8,268\n8,269\n8,270\n8,271\n8,272\n8,273\n8,274\n8,275\n8,276\n8,277\n8,278\n8,279\n8,280\n8,281\n8,3375\n8,3376\n8,3377\n8,3378\n8,3379\n8,3380\n8,3381\n8,3382\n8,3383\n8,3384\n8,3385\n8,3386\n8,3387\n8,3388\n8,3255\n8,282\n8,283\n8,284\n8,285\n8,286\n8,287\n8,288\n8,289\n8,290\n8,291\n8,292\n8,293\n8,294\n8,295\n8,296\n8,297\n8,298\n8,299\n8,300\n8,301\n8,302\n8,303\n8,304\n8,305\n8,306\n8,307\n8,308\n8,309\n8,310\n8,311\n8,312\n8,2591\n8,2592\n8,2593\n8,2594\n8,2595\n8,2596\n8,2597\n8,2598\n8,2599\n8,2600\n8,2601\n8,2602\n8,2603\n8,2604\n8,2605\n8,2606\n8,2607\n8,2608\n8,313\n8,314\n8,315\n8,316\n8,317\n8,318\n8,319\n8,320\n8,321\n8,322\n8,399\n8,3259\n8,675\n8,676\n8,677\n8,678\n8,679\n8,680\n8,681\n8,682\n8,683\n8,684\n8,685\n8,686\n8,687\n8,688\n8,689\n8,690\n8,691\n8,692\n8,693\n8,694\n8,695\n8,696\n8,697\n8,698\n8,699\n8,700\n8,701\n8,702\n8,703\n8,704\n8,705\n8,706\n8,707\n8,708\n8,709\n8,710\n8,711\n8,712\n8,713\n8,714\n8,2609\n8,2610\n8,2611\n8,2612\n8,2613\n8,2614\n8,2615\n8,2616\n8,2617\n8,2618\n8,2619\n8,2620\n8,2621\n8,2622\n8,2623\n8,2624\n8,2625\n8,2626\n8,2627\n8,2628\n8,2629\n8,2630\n8,2631\n8,2632\n8,2633\n8,2634\n8,2635\n8,2636\n8,2637\n8,2638\n8,489\n8,490\n8,491\n8,492\n8,493\n8,494\n8,495\n8,496\n8,497\n8,498\n8,499\n8,500\n8,816\n8,817\n8,818\n8,819\n8,820\n8,821\n8,822\n8,823\n8,824\n8,825\n8,745\n8,746\n8,747\n8,748\n8,749\n8,750\n8,751\n8,752\n8,753\n8,754\n8,755\n8,756\n8,757\n8,758\n8,759\n8,760\n8,620\n8,621\n8,622\n8,623\n8,761\n8,762\n8,763\n8,764\n8,765\n8,766\n8,767\n8,768\n8,769\n8,770\n8,771\n8,772\n8,773\n8,774\n8,775\n8,776\n8,777\n8,778\n8,779\n8,780\n8,781\n8,782\n8,783\n8,784\n8,785\n8,543\n8,544\n8,545\n8,546\n8,547\n8,548\n8,549\n8,786\n8,787\n8,788\n8,789\n8,790\n8,791\n8,792\n8,793\n8,794\n8,795\n8,796\n8,797\n8,798\n8,799\n8,800\n8,801\n8,802\n8,803\n8,804\n8,805\n8,806\n8,807\n8,808\n8,809\n8,810\n8,811\n8,812\n8,813\n8,814\n8,815\n8,826\n8,827\n8,828\n8,829\n8,830\n8,831\n8,832\n8,833\n8,834\n8,835\n8,836\n8,837\n8,838\n8,839\n8,840\n8,841\n8,842\n8,843\n8,844\n8,845\n8,846\n8,847\n8,848\n8,849\n8,850\n8,3260\n8,3331\n8,851\n8,852\n8,853\n8,854\n8,855\n8,856\n8,857\n8,858\n8,859\n8,860\n8,861\n8,862\n8,863\n8,864\n8,865\n8,866\n8,867\n8,868\n8,869\n8,870\n8,871\n8,872\n8,873\n8,874\n8,875\n8,876\n8,2639\n8,2640\n8,2641\n8,2642\n8,2643\n8,2644\n8,2645\n8,2646\n8,2647\n8,2648\n8,2649\n8,3225\n8,583\n8,584\n8,585\n8,586\n8,587\n8,588\n8,589\n8,590\n8,591\n8,592\n8,593\n8,594\n8,595\n8,596\n8,388\n8,402\n8,407\n8,396\n8,877\n8,878\n8,879\n8,880\n8,881\n8,882\n8,883\n8,884\n8,885\n8,886\n8,887\n8,888\n8,889\n8,890\n8,3405\n8,891\n8,892\n8,893\n8,894\n8,895\n8,896\n8,897\n8,898\n8,899\n8,900\n8,901\n8,902\n8,903\n8,904\n8,905\n8,906\n8,907\n8,908\n8,909\n8,910\n8,911\n8,912\n8,913\n8,914\n8,915\n8,916\n8,917\n8,918\n8,919\n8,920\n8,921\n8,922\n8,3423\n8,923\n8,924\n8,925\n8,926\n8,927\n8,928\n8,929\n8,930\n8,931\n8,932\n8,933\n8,934\n8,935\n8,936\n8,937\n8,938\n8,939\n8,940\n8,941\n8,942\n8,943\n8,944\n8,945\n8,946\n8,947\n8,948\n8,949\n8,950\n8,951\n8,952\n8,953\n8,954\n8,955\n8,956\n8,957\n8,958\n8,959\n8,960\n8,961\n8,962\n8,963\n8,964\n8,965\n8,966\n8,967\n8,968\n8,969\n8,970\n8,971\n8,972\n8,973\n8,974\n8,975\n8,976\n8,977\n8,978\n8,979\n8,980\n8,981\n8,982\n8,983\n8,984\n8,985\n8,986\n8,987\n8,988\n8,390\n8,3273\n8,1020\n8,1021\n8,1022\n8,1023\n8,1024\n8,1025\n8,1026\n8,1027\n8,1028\n8,1029\n8,1030\n8,1031\n8,1032\n8,989\n8,990\n8,991\n8,992\n8,993\n8,994\n8,995\n8,996\n8,997\n8,998\n8,999\n8,1000\n8,1001\n8,1002\n8,1003\n8,1004\n8,1005\n8,1006\n8,1007\n8,1008\n8,1009\n8,1010\n8,1011\n8,1012\n8,1013\n8,1014\n8,1015\n8,1016\n8,1017\n8,1018\n8,1019\n8,1033\n8,1034\n8,1035\n8,1036\n8,1037\n8,1038\n8,1039\n8,1040\n8,1041\n8,1042\n8,1043\n8,1044\n8,1045\n8,1046\n8,1047\n8,1048\n8,1049\n8,1050\n8,1051\n8,1052\n8,1053\n8,1054\n8,1055\n8,1056\n8,351\n8,352\n8,353\n8,354\n8,355\n8,356\n8,357\n8,358\n8,359\n8,3332\n8,1057\n8,1058\n8,1059\n8,1060\n8,1061\n8,1062\n8,1063\n8,1064\n8,1065\n8,1066\n8,1067\n8,1068\n8,1069\n8,1070\n8,1071\n8,1072\n8,624\n8,625\n8,626\n8,627\n8,628\n8,629\n8,630\n8,631\n8,632\n8,633\n8,634\n8,635\n8,636\n8,637\n8,638\n8,639\n8,640\n8,641\n8,642\n8,643\n8,644\n8,645\n8,1073\n8,1074\n8,1075\n8,1076\n8,1077\n8,1078\n8,1079\n8,1080\n8,1081\n8,1082\n8,1083\n8,1084\n8,1085\n8,1086\n8,377\n8,395\n8,1102\n8,1103\n8,1104\n8,1087\n8,1088\n8,1089\n8,1090\n8,1091\n8,1092\n8,1093\n8,1094\n8,1095\n8,1096\n8,1097\n8,1098\n8,1099\n8,1100\n8,1101\n8,1105\n8,1106\n8,1107\n8,1108\n8,1109\n8,1110\n8,1111\n8,1112\n8,1113\n8,1114\n8,1115\n8,1116\n8,1117\n8,1118\n8,1119\n8,1120\n8,1121\n8,1122\n8,1123\n8,1124\n8,1125\n8,1126\n8,1127\n8,1128\n8,1129\n8,1130\n8,1131\n8,1132\n8,501\n8,502\n8,503\n8,504\n8,505\n8,506\n8,507\n8,508\n8,509\n8,510\n8,511\n8,512\n8,513\n8,514\n8,1133\n8,1134\n8,1135\n8,1136\n8,1137\n8,1138\n8,1139\n8,1140\n8,1141\n8,1142\n8,1143\n8,1144\n8,1145\n8,3265\n8,468\n8,469\n8,470\n8,471\n8,472\n8,473\n8,474\n8,475\n8,476\n8,477\n8,478\n8,479\n8,480\n8,481\n8,482\n8,483\n8,484\n8,485\n8,486\n8,487\n8,488\n8,1146\n8,1147\n8,1148\n8,1149\n8,1150\n8,1151\n8,1152\n8,1153\n8,1154\n8,1155\n8,1156\n8,1157\n8,1158\n8,1159\n8,1160\n8,1161\n8,1162\n8,1163\n8,1164\n8,1165\n8,1166\n8,1167\n8,1168\n8,1169\n8,1170\n8,1171\n8,1172\n8,1173\n8,1174\n8,1175\n8,1176\n8,1177\n8,1178\n8,1179\n8,1180\n8,1181\n8,1182\n8,1183\n8,1184\n8,1185\n8,1186\n8,1187\n8,3322\n8,3354\n8,3351\n8,3422\n8,405\n8,3407\n8,3301\n8,3300\n8,3302\n8,3303\n8,3304\n8,3305\n8,3306\n8,3307\n8,3308\n8,3309\n8,3310\n8,3311\n8,3312\n8,3313\n8,3314\n8,3315\n8,3316\n8,3317\n8,3318\n8,1188\n8,1189\n8,1190\n8,1191\n8,1192\n8,1193\n8,1194\n8,1195\n8,1196\n8,1197\n8,1198\n8,1199\n8,1200\n8,3329\n8,1235\n8,1236\n8,1237\n8,1238\n8,1239\n8,1240\n8,1241\n8,1242\n8,1243\n8,1244\n8,1245\n8,1246\n8,1247\n8,1248\n8,1249\n8,1250\n8,1251\n8,1252\n8,1253\n8,1254\n8,1255\n8,1256\n8,1257\n8,1258\n8,1259\n8,1260\n8,1261\n8,1262\n8,1263\n8,1264\n8,1265\n8,1266\n8,1267\n8,1268\n8,1269\n8,1270\n8,1271\n8,1272\n8,1273\n8,1274\n8,1275\n8,1276\n8,1277\n8,1278\n8,1279\n8,1280\n8,1281\n8,1282\n8,1283\n8,1284\n8,1285\n8,1286\n8,1287\n8,1288\n8,1289\n8,1290\n8,1291\n8,1292\n8,1293\n8,1294\n8,1295\n8,1296\n8,1297\n8,1298\n8,1299\n8,1300\n8,1301\n8,1302\n8,1303\n8,1304\n8,1305\n8,1306\n8,1307\n8,1308\n8,1309\n8,1310\n8,1311\n8,1312\n8,1313\n8,1314\n8,1315\n8,1316\n8,1317\n8,1318\n8,1319\n8,1320\n8,1321\n8,1322\n8,1323\n8,1324\n8,1201\n8,1202\n8,1203\n8,1204\n8,1205\n8,1206\n8,1207\n8,1208\n8,1209\n8,1210\n8,1211\n8,1325\n8,1326\n8,1327\n8,1328\n8,1329\n8,1330\n8,1331\n8,1332\n8,1333\n8,1334\n8,1391\n8,1393\n8,1388\n8,1394\n8,1387\n8,1392\n8,1389\n8,1390\n8,1335\n8,1336\n8,1337\n8,1338\n8,1339\n8,1340\n8,1341\n8,1342\n8,1343\n8,1344\n8,1345\n8,1346\n8,1347\n8,1348\n8,1349\n8,1350\n8,1351\n8,1212\n8,1213\n8,1214\n8,1215\n8,1216\n8,1217\n8,1218\n8,1219\n8,1220\n8,1221\n8,1222\n8,1223\n8,1224\n8,1225\n8,1226\n8,1227\n8,1228\n8,1229\n8,1230\n8,1231\n8,1232\n8,1233\n8,1234\n8,1352\n8,1353\n8,1354\n8,1355\n8,1356\n8,1357\n8,1358\n8,1359\n8,1360\n8,1361\n8,1362\n8,1363\n8,1364\n8,1365\n8,1366\n8,1367\n8,1368\n8,1369\n8,1370\n8,1371\n8,1372\n8,1373\n8,1374\n8,1375\n8,1376\n8,1377\n8,1378\n8,1379\n8,1380\n8,1381\n8,1382\n8,1386\n8,1383\n8,1385\n8,1384\n8,1406\n8,1407\n8,1408\n8,1409\n8,1410\n8,1411\n8,1412\n8,1413\n8,1395\n8,1396\n8,1397\n8,1398\n8,1399\n8,1400\n8,1401\n8,1402\n8,1403\n8,1404\n8,1405\n8,3274\n8,3267\n8,3261\n8,3272\n8,1414\n8,1415\n8,1416\n8,1417\n8,1418\n8,1419\n8,1420\n8,1421\n8,1422\n8,1423\n8,1424\n8,1425\n8,1426\n8,1427\n8,1428\n8,1429\n8,1430\n8,1431\n8,1432\n8,1433\n8,1434\n8,1435\n8,1436\n8,1437\n8,1438\n8,1439\n8,1440\n8,1441\n8,1442\n8,1443\n8,1455\n8,1456\n8,1457\n8,1458\n8,1459\n8,1460\n8,1461\n8,1462\n8,1463\n8,1464\n8,1465\n8,1444\n8,1445\n8,1446\n8,1447\n8,1448\n8,1449\n8,1450\n8,1451\n8,1452\n8,1453\n8,1454\n8,1466\n8,1467\n8,1468\n8,1469\n8,1470\n8,1471\n8,1472\n8,1473\n8,1474\n8,1475\n8,1476\n8,1477\n8,1478\n8,1479\n8,1480\n8,1481\n8,1482\n8,1483\n8,1484\n8,1485\n8,1486\n8,1487\n8,1488\n8,1489\n8,1490\n8,1491\n8,1492\n8,1493\n8,1494\n8,1495\n8,378\n8,392\n8,1532\n8,1533\n8,1534\n8,1535\n8,1536\n8,1537\n8,1538\n8,1539\n8,1540\n8,1541\n8,1542\n8,1543\n8,1544\n8,1545\n8,1496\n8,1497\n8,1498\n8,1499\n8,1500\n8,1501\n8,1502\n8,1503\n8,1504\n8,1505\n8,403\n8,1506\n8,1507\n8,1508\n8,1509\n8,1510\n8,1511\n8,1512\n8,1513\n8,1514\n8,1515\n8,1516\n8,1517\n8,1518\n8,1519\n8,381\n8,1520\n8,1521\n8,1522\n8,1523\n8,1524\n8,1525\n8,1526\n8,1527\n8,1528\n8,1529\n8,1530\n8,1531\n8,1546\n8,1547\n8,1548\n8,1549\n8,1550\n8,1551\n8,1552\n8,1553\n8,1554\n8,1555\n8,1556\n8,1557\n8,1558\n8,1559\n8,1560\n8,1561\n8,3352\n8,3358\n8,400\n8,436\n8,437\n8,438\n8,439\n8,440\n8,441\n8,442\n8,443\n8,444\n8,445\n8,446\n8,447\n8,448\n8,449\n8,450\n8,451\n8,452\n8,453\n8,454\n8,455\n8,1562\n8,1563\n8,1564\n8,1565\n8,1566\n8,1567\n8,1568\n8,1569\n8,1570\n8,1571\n8,1572\n8,1573\n8,1574\n8,1575\n8,1576\n8,337\n8,338\n8,339\n8,340\n8,341\n8,342\n8,343\n8,344\n8,345\n8,346\n8,347\n8,348\n8,349\n8,350\n8,1577\n8,1578\n8,1579\n8,1580\n8,1581\n8,1582\n8,1583\n8,1584\n8,1585\n8,1586\n8,1587\n8,1588\n8,1589\n8,1590\n8,1591\n8,1592\n8,1593\n8,1594\n8,1595\n8,1596\n8,1597\n8,1598\n8,1599\n8,1600\n8,1601\n8,1602\n8,1603\n8,1604\n8,1605\n8,1606\n8,1607\n8,1608\n8,1609\n8,1610\n8,1611\n8,1612\n8,1613\n8,1614\n8,1615\n8,1616\n8,1617\n8,1618\n8,1619\n8,1620\n8,1621\n8,1622\n8,1623\n8,1624\n8,1625\n8,1626\n8,1627\n8,1628\n8,1629\n8,1630\n8,1631\n8,1632\n8,1633\n8,1634\n8,1635\n8,1636\n8,1637\n8,1638\n8,1639\n8,1640\n8,1641\n8,1642\n8,1643\n8,1644\n8,1645\n8,550\n8,551\n8,552\n8,553\n8,554\n8,555\n8,1646\n8,1647\n8,1648\n8,1649\n8,1650\n8,1651\n8,1652\n8,1653\n8,1654\n8,1655\n8,1656\n8,1657\n8,1658\n8,1659\n8,1660\n8,1661\n8,1662\n8,1663\n8,1664\n8,1665\n8,1666\n8,1667\n8,1668\n8,1669\n8,1670\n8,1686\n8,1687\n8,1688\n8,1689\n8,1690\n8,1691\n8,1692\n8,1693\n8,1694\n8,1695\n8,1696\n8,1697\n8,1698\n8,1699\n8,1700\n8,1701\n8,1671\n8,1672\n8,1673\n8,1674\n8,1675\n8,1676\n8,1677\n8,1678\n8,1679\n8,1680\n8,1681\n8,1682\n8,1683\n8,1684\n8,1685\n8,1702\n8,1703\n8,1704\n8,1705\n8,1706\n8,1707\n8,1708\n8,1709\n8,1710\n8,1711\n8,1712\n8,1713\n8,1714\n8,1715\n8,1716\n8,3257\n8,3425\n8,3420\n8,3326\n8,3258\n8,3356\n8,3424\n8,384\n8,1717\n8,1720\n8,1722\n8,1723\n8,1726\n8,1727\n8,1730\n8,1731\n8,1733\n8,1736\n8,1737\n8,1740\n8,1742\n8,1743\n8,1718\n8,1719\n8,1721\n8,1724\n8,1725\n8,1728\n8,1729\n8,1732\n8,1734\n8,1735\n8,1738\n8,1739\n8,1741\n8,1744\n8,374\n8,1745\n8,1746\n8,1747\n8,1748\n8,1749\n8,1750\n8,1751\n8,1752\n8,1753\n8,1754\n8,1755\n8,1762\n8,1763\n8,1756\n8,1764\n8,1757\n8,1758\n8,1765\n8,1766\n8,1759\n8,1760\n8,1767\n8,1761\n8,1768\n8,1769\n8,1770\n8,1771\n8,1772\n8,1773\n8,1774\n8,1775\n8,1776\n8,1777\n8,1778\n8,1779\n8,1780\n8,1781\n8,1782\n8,1783\n8,1784\n8,1785\n8,1786\n8,1787\n8,1788\n8,1789\n8,1790\n8,3270\n8,1791\n8,1792\n8,1793\n8,1794\n8,1795\n8,1796\n8,1797\n8,1798\n8,1799\n8,1800\n8,1893\n8,1894\n8,1895\n8,1896\n8,1897\n8,1898\n8,1899\n8,1900\n8,1901\n8,1801\n8,1802\n8,1803\n8,1804\n8,1805\n8,1806\n8,1807\n8,1808\n8,1809\n8,1810\n8,1811\n8,1812\n8,408\n8,409\n8,410\n8,411\n8,412\n8,413\n8,414\n8,415\n8,416\n8,417\n8,418\n8,1813\n8,1814\n8,1815\n8,1816\n8,1817\n8,1818\n8,1819\n8,1820\n8,1821\n8,1822\n8,1823\n8,1824\n8,1825\n8,1826\n8,1827\n8,1828\n8,1829\n8,1830\n8,1831\n8,1832\n8,1833\n8,1834\n8,1835\n8,1836\n8,1837\n8,1838\n8,1839\n8,1840\n8,1841\n8,1842\n8,1843\n8,1844\n8,1845\n8,1846\n8,1847\n8,1848\n8,1849\n8,1850\n8,1851\n8,1852\n8,1853\n8,1854\n8,1855\n8,1856\n8,1857\n8,1858\n8,1859\n8,1860\n8,1861\n8,1862\n8,1863\n8,1864\n8,1865\n8,1866\n8,1867\n8,1868\n8,1869\n8,1870\n8,1871\n8,1872\n8,1873\n8,1874\n8,1875\n8,1876\n8,1877\n8,1878\n8,1879\n8,1880\n8,1881\n8,1882\n8,1883\n8,1884\n8,1885\n8,1886\n8,1887\n8,1888\n8,1889\n8,1890\n8,1891\n8,1892\n8,597\n8,598\n8,599\n8,600\n8,601\n8,602\n8,603\n8,604\n8,605\n8,606\n8,607\n8,608\n8,609\n8,610\n8,611\n8,612\n8,613\n8,614\n8,615\n8,616\n8,617\n8,618\n8,619\n8,1902\n8,1903\n8,1904\n8,1905\n8,1906\n8,1907\n8,1908\n8,1909\n8,1910\n8,1911\n8,1912\n8,1913\n8,1914\n8,1915\n8,398\n8,1916\n8,1917\n8,1918\n8,1919\n8,1920\n8,1921\n8,1922\n8,1923\n8,1924\n8,1925\n8,1926\n8,1927\n8,1928\n8,1929\n8,1930\n8,1931\n8,1932\n8,1933\n8,1934\n8,1935\n8,1936\n8,1937\n8,1938\n8,1939\n8,1940\n8,1941\n8,375\n8,1957\n8,1958\n8,1959\n8,1960\n8,1961\n8,1962\n8,1963\n8,1964\n8,1965\n8,1966\n8,1967\n8,1968\n8,1969\n8,1970\n8,1971\n8,1972\n8,1973\n8,1974\n8,1975\n8,1976\n8,1977\n8,1978\n8,1979\n8,1980\n8,1981\n8,1982\n8,1983\n8,1984\n8,1985\n8,1942\n8,1943\n8,1944\n8,1945\n8,1946\n8,1947\n8,1948\n8,1949\n8,1950\n8,1951\n8,1952\n8,1953\n8,1954\n8,1955\n8,1956\n8,3327\n8,3330\n8,385\n8,3321\n8,383\n8,3359\n8,1986\n8,1987\n8,1988\n8,1989\n8,1990\n8,1991\n8,1992\n8,1993\n8,1994\n8,1995\n8,1996\n8,1997\n8,1998\n8,1999\n8,2000\n8,2001\n8,2002\n8,2003\n8,2004\n8,2005\n8,2006\n8,2007\n8,2008\n8,2009\n8,2010\n8,2011\n8,2012\n8,2013\n8,2014\n8,387\n8,3319\n8,2015\n8,2016\n8,2017\n8,2018\n8,2019\n8,2020\n8,2021\n8,2022\n8,2023\n8,2024\n8,2025\n8,2026\n8,2027\n8,2028\n8,2029\n8,2030\n8,2031\n8,2032\n8,2033\n8,2034\n8,2035\n8,2036\n8,2037\n8,2038\n8,2039\n8,2040\n8,2041\n8,2042\n8,2043\n8,3415\n8,393\n8,529\n8,530\n8,531\n8,532\n8,533\n8,534\n8,535\n8,536\n8,537\n8,538\n8,539\n8,540\n8,541\n8,542\n8,2044\n8,2045\n8,2046\n8,2047\n8,2048\n8,2049\n8,2050\n8,2051\n8,2052\n8,2053\n8,2054\n8,2055\n8,2056\n8,2057\n8,2058\n8,2059\n8,2060\n8,2061\n8,2062\n8,2063\n8,2064\n8,2065\n8,2066\n8,2067\n8,2068\n8,2069\n8,2070\n8,2071\n8,2072\n8,2073\n8,2074\n8,2075\n8,2076\n8,2077\n8,2078\n8,2079\n8,2080\n8,2081\n8,2082\n8,2083\n8,2084\n8,2085\n8,2086\n8,2087\n8,2088\n8,2089\n8,2090\n8,2091\n8,2092\n8,3328\n8,2093\n8,2094\n8,2095\n8,2096\n8,2097\n8,2098\n8,3276\n8,3277\n8,3278\n8,3279\n8,3280\n8,3281\n8,3282\n8,3283\n8,3284\n8,3285\n8,3286\n8,3287\n8,2099\n8,2100\n8,2101\n8,2102\n8,2103\n8,2104\n8,2105\n8,2106\n8,2107\n8,2108\n8,2109\n8,2110\n8,2111\n8,2112\n8,2113\n8,2114\n8,2115\n8,2116\n8,2117\n8,2118\n8,2119\n8,2120\n8,2121\n8,2122\n8,2123\n8,2124\n8,2125\n8,2126\n8,2127\n8,2128\n8,2129\n8,2130\n8,2131\n8,2132\n8,2133\n8,2134\n8,2135\n8,2136\n8,2137\n8,2138\n8,2139\n8,2140\n8,2141\n8,2142\n8,2143\n8,2144\n8,2145\n8,2146\n8,2147\n8,2148\n8,2149\n8,2150\n8,2151\n8,2152\n8,2153\n8,2154\n8,2155\n8,2156\n8,2157\n8,2158\n8,2159\n8,2160\n8,2161\n8,2162\n8,2163\n8,2164\n8,2165\n8,2166\n8,2167\n8,2168\n8,2169\n8,2170\n8,2171\n8,2172\n8,2173\n8,2174\n8,2175\n8,2176\n8,2177\n8,2178\n8,2179\n8,2180\n8,2181\n8,2182\n8,2183\n8,2184\n8,2185\n8,2186\n8,2187\n8,2188\n8,2189\n8,2190\n8,2191\n8,2192\n8,2193\n8,2194\n8,2195\n8,2196\n8,2197\n8,2198\n8,2199\n8,2200\n8,2201\n8,2202\n8,2203\n8,2204\n8,2205\n8,2206\n8,2207\n8,2208\n8,2209\n8,2210\n8,2211\n8,2212\n8,2213\n8,2214\n8,2215\n8,386\n8,3325\n8,2216\n8,2217\n8,2218\n8,2219\n8,2220\n8,2221\n8,2222\n8,2223\n8,2224\n8,2225\n8,2226\n8,2227\n8,2228\n8,2229\n8,2230\n8,2231\n8,2232\n8,2233\n8,2234\n8,2235\n8,2236\n8,2237\n8,2238\n8,2239\n8,2240\n8,2241\n8,2242\n8,2243\n8,2244\n8,2245\n8,2246\n8,2247\n8,2248\n8,2249\n8,2250\n8,2251\n8,2252\n8,2253\n8,2650\n8,2651\n8,2652\n8,2653\n8,2654\n8,2655\n8,2656\n8,2657\n8,2658\n8,2659\n8,2660\n8,2661\n8,2662\n8,2663\n8,3353\n8,3355\n8,3271\n8,2254\n8,2255\n8,2256\n8,2257\n8,2258\n8,2259\n8,2260\n8,2261\n8,2262\n8,2263\n8,2264\n8,2265\n8,2266\n8,2267\n8,2268\n8,2269\n8,2270\n8,419\n8,420\n8,421\n8,422\n8,423\n8,424\n8,425\n8,426\n8,427\n8,428\n8,429\n8,430\n8,431\n8,432\n8,433\n8,434\n8,435\n8,2271\n8,2272\n8,2273\n8,2274\n8,2275\n8,2276\n8,2277\n8,2278\n8,2279\n8,2280\n8,2281\n8,2318\n8,2319\n8,2320\n8,2321\n8,2322\n8,2323\n8,2324\n8,2325\n8,2326\n8,2327\n8,2328\n8,2329\n8,2330\n8,2331\n8,2332\n8,2333\n8,2285\n8,2286\n8,2287\n8,2288\n8,2289\n8,2290\n8,2291\n8,2292\n8,2293\n8,2294\n8,2295\n8,3254\n8,2296\n8,2297\n8,2298\n8,2299\n8,2300\n8,2301\n8,2302\n8,2303\n8,2304\n8,2305\n8,2306\n8,2307\n8,2308\n8,2309\n8,2310\n8,2311\n8,2312\n8,2313\n8,2314\n8,2315\n8,2316\n8,2317\n8,2282\n8,2283\n8,2284\n8,2334\n8,2335\n8,2336\n8,2337\n8,2338\n8,2339\n8,2340\n8,2341\n8,2342\n8,2343\n8,2344\n8,2345\n8,2346\n8,2347\n8,2348\n8,2349\n8,2350\n8,2351\n8,2352\n8,2353\n8,2354\n8,2355\n8,2356\n8,2357\n8,2358\n8,2359\n8,2360\n8,2361\n8,2362\n8,2363\n8,2364\n8,2365\n8,2366\n8,2367\n8,2368\n8,2369\n8,2370\n8,2371\n8,2372\n8,2373\n8,2374\n8,2375\n8,2376\n8,2377\n8,2378\n8,2379\n8,2380\n8,2381\n8,2382\n8,2383\n8,2384\n8,2385\n8,2386\n8,2387\n8,2388\n8,2389\n8,2390\n8,2391\n8,2392\n8,2393\n8,2394\n8,2395\n8,2396\n8,2397\n8,2398\n8,2399\n8,2400\n8,2401\n8,2402\n8,2403\n8,2404\n8,2405\n8,3275\n8,3404\n8,3323\n8,2664\n8,2665\n8,2666\n8,2667\n8,2668\n8,2669\n8,2670\n8,2671\n8,2672\n8,2673\n8,2674\n8,2675\n8,2676\n8,2677\n8,2678\n8,2679\n8,2680\n8,2681\n8,2682\n8,2683\n8,2684\n8,2685\n8,2686\n8,2687\n8,2688\n8,2689\n8,2690\n8,2691\n8,2692\n8,2693\n8,2694\n8,2695\n8,2696\n8,2697\n8,2698\n8,2699\n8,2700\n8,2701\n8,2702\n8,2703\n8,2704\n8,3414\n8,2406\n8,2407\n8,2408\n8,2409\n8,2410\n8,2411\n8,2412\n8,2413\n8,2414\n8,2415\n8,2416\n8,2417\n8,2418\n8,2419\n8,3334\n8,401\n8,2420\n8,2421\n8,2422\n8,2423\n8,2424\n8,2425\n8,2426\n8,2427\n8,2428\n8,2429\n8,2430\n8,2431\n8,2432\n8,2433\n8,570\n8,573\n8,577\n8,580\n8,581\n8,571\n8,579\n8,582\n8,572\n8,575\n8,578\n8,574\n8,576\n8,3410\n8,3288\n8,3289\n8,3290\n8,3291\n8,3292\n8,3293\n8,3294\n8,3295\n8,3296\n8,3297\n8,3298\n8,3299\n8,3333\n8,2434\n8,2435\n8,2436\n8,2437\n8,2438\n8,2439\n8,2440\n8,2441\n8,2442\n8,2443\n8,2444\n8,2445\n8,2446\n8,2447\n8,2448\n8,3418\n8,2449\n8,2450\n8,2451\n8,2452\n8,2453\n8,2454\n8,2455\n8,2456\n8,2457\n8,2458\n8,2459\n8,2460\n8,2461\n8,2462\n8,2463\n8,2464\n8,2465\n8,2466\n8,2467\n8,2468\n8,2469\n8,2470\n8,2471\n8,2472\n8,2473\n8,2474\n8,2475\n8,2476\n8,2477\n8,2478\n8,2479\n8,2480\n8,2481\n8,2482\n8,2483\n8,2484\n8,2485\n8,2486\n8,2487\n8,2488\n8,2489\n8,2490\n8,2491\n8,2492\n8,2493\n8,2494\n8,2495\n8,2496\n8,2497\n8,2498\n8,2499\n8,2500\n8,2501\n8,2502\n8,2503\n8,2504\n8,2505\n8,3269\n8,2506\n8,2507\n8,2508\n8,2509\n8,2510\n8,2511\n8,2512\n8,2513\n8,2514\n8,2515\n8,2516\n8,2517\n8,2518\n8,2519\n8,2520\n8,2521\n8,2522\n8,456\n8,457\n8,458\n8,459\n8,460\n8,461\n8,462\n8,463\n8,464\n8,465\n8,466\n8,467\n8,2523\n8,2524\n8,2525\n8,2526\n8,2527\n8,2528\n8,2529\n8,2530\n8,2531\n8,3335\n8,2532\n8,2533\n8,2534\n8,2535\n8,2536\n8,2537\n8,2538\n8,2539\n8,2540\n8,2541\n8,2542\n8,2543\n8,2544\n8,2545\n8,2546\n8,2547\n8,2548\n8,2549\n8,2550\n8,2551\n8,2552\n8,2553\n8,2554\n8,2555\n8,2556\n8,2557\n8,2558\n8,2559\n8,2560\n8,2561\n8,2562\n8,2563\n8,2564\n8,2705\n8,2706\n8,2707\n8,2708\n8,2709\n8,2710\n8,2711\n8,2712\n8,2713\n8,2714\n8,2715\n8,2716\n8,2717\n8,2718\n8,2719\n8,2720\n8,2721\n8,2722\n8,2723\n8,2724\n8,2725\n8,2726\n8,2727\n8,2728\n8,2729\n8,2730\n8,3365\n8,3366\n8,3367\n8,3368\n8,3369\n8,3370\n8,3371\n8,3372\n8,3373\n8,3374\n8,2565\n8,2566\n8,2567\n8,2568\n8,2569\n8,2570\n8,2571\n8,2751\n8,2752\n8,2753\n8,2754\n8,2755\n8,2756\n8,2757\n8,2758\n8,2759\n8,2760\n8,2761\n8,2762\n8,2763\n8,2764\n8,2765\n8,2766\n8,2767\n8,2768\n8,2769\n8,2770\n8,2771\n8,2772\n8,2773\n8,2774\n8,2775\n8,2776\n8,2777\n8,2778\n8,2779\n8,2780\n8,2781\n8,2782\n8,2783\n8,2784\n8,2785\n8,2786\n8,2787\n8,2788\n8,2789\n8,2790\n8,2791\n8,2792\n8,2793\n8,2794\n8,2795\n8,2796\n8,2797\n8,2798\n8,2799\n8,2800\n8,2801\n8,2802\n8,2803\n8,2804\n8,2805\n8,2806\n8,2807\n8,2808\n8,2809\n8,2810\n8,2811\n8,2812\n8,2813\n8,2814\n8,2815\n8,2816\n8,2817\n8,2818\n8,646\n8,647\n8,648\n8,649\n8,651\n8,653\n8,655\n8,658\n8,2926\n8,2927\n8,2928\n8,2929\n8,2930\n8,2931\n8,2932\n8,2933\n8,2934\n8,2935\n8,2936\n8,2937\n8,2938\n8,2939\n8,2940\n8,2941\n8,2942\n8,2943\n8,2944\n8,2945\n8,2946\n8,2947\n8,2948\n8,2949\n8,2950\n8,2951\n8,2952\n8,2953\n8,2954\n8,2955\n8,2956\n8,2957\n8,2958\n8,2959\n8,2960\n8,2961\n8,2962\n8,2963\n8,3004\n8,3005\n8,3006\n8,3007\n8,3008\n8,3009\n8,3010\n8,3011\n8,3012\n8,3013\n8,3014\n8,3015\n8,3016\n8,3017\n8,2964\n8,2965\n8,2966\n8,2967\n8,2968\n8,2969\n8,2970\n8,2971\n8,2972\n8,2973\n8,2974\n8,3253\n8,2975\n8,2976\n8,2977\n8,2978\n8,2979\n8,2980\n8,2981\n8,2982\n8,2983\n8,2984\n8,2985\n8,2986\n8,2987\n8,2988\n8,2989\n8,2990\n8,2991\n8,2992\n8,2993\n8,2994\n8,2995\n8,2996\n8,2997\n8,2998\n8,2999\n8,3000\n8,3001\n8,3002\n8,3003\n8,3018\n8,3019\n8,3020\n8,3021\n8,3022\n8,3023\n8,3024\n8,3025\n8,3026\n8,3027\n8,3028\n8,3029\n8,3030\n8,3031\n8,3032\n8,3033\n8,3034\n8,3035\n8,3036\n8,3037\n8,3038\n8,3039\n8,3040\n8,3041\n8,3042\n8,3043\n8,3044\n8,3045\n8,3046\n8,3047\n8,3048\n8,3049\n8,3050\n8,3051\n8,3064\n8,3065\n8,3066\n8,3067\n8,3068\n8,3069\n8,3070\n8,3071\n8,3072\n8,3073\n8,3074\n8,3075\n8,3076\n8,3077\n8,3078\n8,3079\n8,3080\n8,3052\n8,3053\n8,3054\n8,3055\n8,3056\n8,3057\n8,3058\n8,3059\n8,3060\n8,3061\n8,3062\n8,3063\n8,3081\n8,3082\n8,3083\n8,3084\n8,3085\n8,3086\n8,3087\n8,3088\n8,3089\n8,3090\n8,3091\n8,3092\n8,3093\n8,3094\n8,3095\n8,3096\n8,3097\n8,3098\n8,3099\n8,3100\n8,3101\n8,3102\n8,3103\n8,323\n8,324\n8,325\n8,326\n8,327\n8,328\n8,329\n8,330\n8,331\n8,332\n8,333\n8,334\n8,335\n8,336\n8,360\n8,361\n8,362\n8,363\n8,364\n8,365\n8,366\n8,367\n8,368\n8,369\n8,370\n8,371\n8,372\n8,373\n8,556\n8,557\n8,558\n8,559\n8,560\n8,561\n8,562\n8,563\n8,564\n8,565\n8,566\n8,567\n8,568\n8,569\n8,661\n8,662\n8,663\n8,664\n8,665\n8,666\n8,667\n8,668\n8,669\n8,670\n8,671\n8,672\n8,673\n8,674\n8,3104\n8,3105\n8,3106\n8,3107\n8,3108\n8,3109\n8,3110\n8,3111\n8,3112\n8,3113\n8,3114\n8,3115\n8,3116\n8,3117\n8,3118\n8,3119\n8,3120\n8,3121\n8,3122\n8,3123\n8,3124\n8,3125\n8,3126\n8,3127\n8,3128\n8,3129\n8,3130\n8,3131\n8,652\n8,656\n8,657\n8,650\n8,659\n8,654\n8,660\n8,3132\n8,3133\n8,3134\n8,3135\n8,3136\n8,3137\n8,3138\n8,3139\n8,3140\n8,3141\n8,3142\n8,3143\n8,3144\n8,3145\n8,2731\n8,2732\n8,2733\n8,2734\n8,2735\n8,2736\n8,2737\n8,2738\n8,2739\n8,2740\n8,2741\n8,2742\n8,2743\n8,2744\n8,2745\n8,2746\n8,2747\n8,2748\n8,2749\n8,2750\n8,3408\n8,3320\n8,3409\n8,3264\n8,3146\n8,3147\n8,3148\n8,3149\n8,3150\n8,3151\n8,3152\n8,3153\n8,3154\n8,3155\n8,3156\n8,3157\n8,3158\n8,3159\n8,3160\n8,3161\n8,3162\n8,3163\n8,3164\n8,3438\n8,3442\n8,3436\n8,3450\n8,3454\n8,3432\n8,3443\n8,3447\n8,3452\n8,3441\n8,3434\n8,3449\n8,3445\n8,3440\n8,3453\n8,3439\n8,3435\n8,3448\n8,3437\n8,3446\n8,3444\n8,3433\n8,3431\n8,3451\n8,3430\n8,3455\n8,3456\n8,3457\n8,3458\n8,3459\n8,3460\n8,3461\n8,3462\n8,3463\n8,3464\n8,3465\n8,3466\n8,3467\n8,3468\n8,3469\n8,3470\n8,3471\n8,3472\n8,3473\n8,3474\n8,3475\n8,3476\n8,3477\n8,3478\n8,3482\n8,3485\n8,3491\n8,3501\n8,3487\n8,3500\n8,3488\n8,3499\n8,3497\n8,3494\n8,3495\n8,3490\n8,3489\n8,3492\n8,3483\n8,3493\n8,3498\n8,3496\n8,3502\n8,3479\n8,3481\n8,3503\n8,3486\n8,3480\n8,3484\n9,3402\n10,3250\n10,2819\n10,2820\n10,2821\n10,2822\n10,2823\n10,2824\n10,2825\n10,2826\n10,2827\n10,2828\n10,2829\n10,2830\n10,2831\n10,2832\n10,2833\n10,2834\n10,2835\n10,2836\n10,2837\n10,2838\n10,3226\n10,3227\n10,3228\n10,3229\n10,3230\n10,3231\n10,3232\n10,3233\n10,3234\n10,3235\n10,3236\n10,3237\n10,3238\n10,3239\n10,3240\n10,3241\n10,3242\n10,3243\n10,3244\n10,3245\n10,3246\n10,3247\n10,3248\n10,3249\n10,2839\n10,2840\n10,2841\n10,2842\n10,2843\n10,2844\n10,2845\n10,2846\n10,2847\n10,2848\n10,2849\n10,2850\n10,2851\n10,2852\n10,2853\n10,2854\n10,2855\n10,2856\n10,3166\n10,3167\n10,3168\n10,3171\n10,3223\n10,2858\n10,2861\n10,2865\n10,2868\n10,2871\n10,2873\n10,2877\n10,2880\n10,2883\n10,2885\n10,2888\n10,2893\n10,2894\n10,2898\n10,2901\n10,2904\n10,2906\n10,2911\n10,2913\n10,2915\n10,2917\n10,2919\n10,2921\n10,2923\n10,2925\n10,2859\n10,2860\n10,2864\n10,2867\n10,2869\n10,2872\n10,2878\n10,2879\n10,2884\n10,2887\n10,2889\n10,2892\n10,2896\n10,2897\n10,2902\n10,2905\n10,2907\n10,2910\n10,2914\n10,2916\n10,2918\n10,2920\n10,2922\n10,2924\n10,2857\n10,2862\n10,2863\n10,2866\n10,2870\n10,2874\n10,2875\n10,2876\n10,2881\n10,2882\n10,2886\n10,2890\n10,2891\n10,2895\n10,2899\n10,2900\n10,2903\n10,2908\n10,2909\n10,2912\n10,3165\n10,3169\n10,3170\n10,3252\n10,3224\n10,3251\n10,3340\n10,3339\n10,3338\n10,3337\n10,3341\n10,3345\n10,3342\n10,3346\n10,3343\n10,3347\n10,3344\n10,3348\n10,3360\n10,3361\n10,3362\n10,3363\n10,3364\n10,3172\n10,3173\n10,3174\n10,3175\n10,3176\n10,3177\n10,3178\n10,3179\n10,3180\n10,3181\n10,3182\n10,3183\n10,3184\n10,3185\n10,3186\n10,3187\n10,3188\n10,3189\n10,3190\n10,3191\n10,3192\n10,3193\n10,3194\n10,3195\n10,3196\n10,3197\n10,3198\n10,3199\n10,3200\n10,3201\n10,3202\n10,3203\n10,3204\n10,3205\n10,3206\n10,3207\n10,3208\n10,3209\n10,3210\n10,3211\n10,3212\n10,3213\n10,3214\n10,3215\n10,3216\n10,3217\n10,3218\n10,3219\n10,3220\n10,3221\n10,3222\n10,3428\n10,3429\n11,391\n11,516\n11,523\n11,219\n11,220\n11,215\n11,730\n11,738\n11,228\n11,230\n11,236\n11,852\n11,858\n11,864\n11,867\n11,874\n11,877\n11,885\n11,888\n11,1088\n11,1093\n11,1099\n11,1105\n11,501\n11,504\n11,1518\n11,1519\n11,1514\n11,1916\n11,1928\n11,1921\n11,2752\n11,2753\n11,2754\n11,2758\n11,2767\n11,2768\n11,2769\n11,393\n12,3479\n12,3480\n12,3481\n12,3482\n12,3483\n12,3484\n12,3485\n12,3486\n12,3487\n12,3488\n12,3489\n12,3490\n12,3491\n12,3492\n12,3493\n12,3494\n12,3495\n12,3496\n12,3497\n12,3498\n12,3499\n12,3500\n12,3501\n12,3502\n12,3503\n12,3430\n12,3431\n12,3432\n12,3433\n12,3434\n12,3435\n12,3436\n12,3437\n12,3438\n12,3439\n12,3440\n12,3441\n12,3442\n12,3443\n12,3444\n12,3445\n12,3446\n12,3447\n12,3448\n12,3449\n12,3450\n12,3451\n12,3452\n12,3453\n12,3454\n12,3403\n12,3404\n12,3405\n12,3406\n12,3407\n12,3408\n12,3409\n12,3410\n12,3411\n12,3412\n12,3413\n12,3414\n12,3415\n12,3416\n12,3417\n12,3418\n12,3419\n12,3420\n12,3421\n12,3422\n12,3423\n12,3424\n12,3425\n12,3426\n12,3427\n13,3479\n13,3480\n13,3481\n13,3482\n13,3483\n13,3484\n13,3485\n13,3486\n13,3487\n13,3488\n13,3489\n13,3490\n13,3491\n13,3492\n13,3493\n13,3494\n13,3495\n13,3496\n13,3497\n13,3498\n13,3499\n13,3500\n13,3501\n13,3502\n13,3503\n14,3430\n14,3431\n14,3432\n14,3433\n14,3434\n14,3435\n14,3436\n14,3437\n14,3438\n14,3439\n14,3440\n14,3441\n14,3442\n14,3443\n14,3444\n14,3445\n14,3446\n14,3447\n14,3448\n14,3449\n14,3450\n14,3451\n14,3452\n14,3453\n14,3454\n15,3403\n15,3404\n15,3405\n15,3406\n15,3407\n15,3408\n15,3409\n15,3410\n15,3411\n15,3412\n15,3413\n15,3414\n15,3415\n15,3416\n15,3417\n15,3418\n15,3419\n15,3420\n15,3421\n15,3422\n15,3423\n15,3424\n15,3425\n15,3426\n15,3427\n16,3367\n16,52\n16,2194\n16,2195\n16,2198\n16,2206\n16,2512\n16,2516\n16,2550\n16,2003\n16,2004\n16,2005\n16,2007\n16,2010\n16,2013\n17,1\n17,2\n17,3\n17,4\n17,5\n17,152\n17,160\n17,1278\n17,1283\n17,1392\n17,1335\n17,1345\n17,1380\n17,1801\n17,1830\n17,1837\n17,1854\n17,1876\n17,1880\n17,1984\n17,1942\n17,1945\n17,2094\n17,2095\n17,2096\n17,3290\n18,597\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/playlists.csv",
    "content": "playlist_id,name\n1,Music\n2,Movies\n3,TV Shows\n4,Audiobooks\n5,90’s Music\n6,Audiobooks\n7,Movies\n8,Music\n9,Music Videos\n10,TV Shows\n11,Brazilian Music\n12,Classical\n13,Classical 101 - Deep Cuts\n14,Classical 101 - Next Steps\n15,Classical 101 - The Basics\n16,Grunge\n17,Heavy Metal Classic\n18,On-The-Go 1\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/schema.sql",
    "content": "DROP TABLE IF EXISTS invoices;\nDROP TABLE IF EXISTS customers;\nDROP TABLE IF EXISTS employees;\nDROP TABLE IF EXISTS tracks;\nDROP TABLE IF EXISTS albums;\nDROP TABLE IF EXISTS genres;\nDROP TABLE IF EXISTS playlist_track;\nDROP TABLE IF EXISTS playlists;\nDROP TABLE IF EXISTS media_types;\nDROP TABLE IF EXISTS artists;\nDROP TABLE IF EXISTS invoice_items;\n\nCREATE TABLE invoices (\n    invoice_id INTEGER,\n    customer_id INTEGER,\n    invoice_date TIMESTAMP,\n    billing_address VARCHAR(255),\n    billing_city VARCHAR(255),\n    billing_state VARCHAR(255),\n    billing_country VARCHAR(255),\n    billing_postal_code VARCHAR(255),\n    total REAL\n);\nCREATE TABLE customers (\n    customer_id INTEGER,\n    first_name VARCHAR(255),\n    last_name VARCHAR(255),\n    company VARCHAR(255),\n    address VARCHAR(255),\n    city VARCHAR(255),\n    state VARCHAR(255),\n    country VARCHAR(255),\n    postal_code VARCHAR(255),\n    phone VARCHAR(255),\n    fax VARCHAR(255),\n    email VARCHAR(255),\n    support_rep_id INTEGER\n);\nCREATE TABLE employees (\n    employee_id INTEGER,\n    last_name VARCHAR(255),\n    first_name VARCHAR(255),\n    title VARCHAR(255),\n    reports_to INTEGER,\n    birth_date TIMESTAMP,\n    hire_date TIMESTAMP,\n    address VARCHAR(255),\n    city VARCHAR(255),\n    state VARCHAR(255),\n    country VARCHAR(255),\n    postal_code VARCHAR(255),\n    phone VARCHAR(255),\n    fax VARCHAR(255),\n    email VARCHAR(255)\n);\nCREATE TABLE tracks (\n    track_id INTEGER,\n    name VARCHAR(255),\n    album_id INTEGER,\n    media_type_id INTEGER,\n    genre_id INTEGER,\n    composer VARCHAR(255),\n    milliseconds INTEGER,\n    bytes INTEGER,\n    unit_price REAL\n);\nCREATE TABLE albums (album_id INTEGER, title VARCHAR(255), artist_id INTEGER);\nCREATE TABLE genres (genre_id INTEGER, name VARCHAR(255));\nCREATE TABLE playlist_track (playlist_id INTEGER, track_id INTEGER);\nCREATE TABLE playlists (playlist_id INTEGER, name VARCHAR(255));\nCREATE TABLE media_types (media_type_id INTEGER, name VARCHAR(255));\nCREATE TABLE artists (artist_id INTEGER, name VARCHAR(255));\nCREATE TABLE invoice_items (\n    invoice_line_id INTEGER,\n    invoice_id INTEGER,\n    track_id INTEGER,\n    unit_price REAL,\n    quantity INTEGER\n);\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/data/chinook/tracks.csv",
    "content": "track_id,name,album_id,media_type_id,genre_id,composer,milliseconds,bytes,unit_price\n1,For Those About To Rock (We Salute You),1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",343719,11170334,0.99\n2,Balls to the Wall,2,2,1,,342562,5510424,0.99\n3,Fast As a Shark,3,2,1,\"F. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman\",230619,3990994,0.99\n4,Restless and Wild,3,2,1,\"F. Baltes, R.A. Smith-Diesel, S. Kaufman, U. Dirkscneider & W. Hoffman\",252051,4331779,0.99\n5,Princess of the Dawn,3,2,1,Deaffy & R.A. Smith-Diesel,375418,6290521,0.99\n6,Put The Finger On You,1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",205662,6713451,0.99\n7,Let's Get It Up,1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",233926,7636561,0.99\n8,Inject The Venom,1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",210834,6852860,0.99\n9,Snowballed,1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",203102,6599424,0.99\n10,Evil Walks,1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",263497,8611245,0.99\n11,C.O.D.,1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",199836,6566314,0.99\n12,Breaking The Rules,1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",263288,8596840,0.99\n13,Night Of The Long Knives,1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",205688,6706347,0.99\n14,Spellbound,1,1,1,\"Angus Young, Malcolm Young, Brian Johnson\",270863,8817038,0.99\n15,Go Down,4,1,1,AC/DC,331180,10847611,0.99\n16,Dog Eat Dog,4,1,1,AC/DC,215196,7032162,0.99\n17,Let There Be Rock,4,1,1,AC/DC,366654,12021261,0.99\n18,Bad Boy Boogie,4,1,1,AC/DC,267728,8776140,0.99\n19,Problem Child,4,1,1,AC/DC,325041,10617116,0.99\n20,Overdose,4,1,1,AC/DC,369319,12066294,0.99\n21,Hell Ain't A Bad Place To Be,4,1,1,AC/DC,254380,8331286,0.99\n22,Whole Lotta Rosie,4,1,1,AC/DC,323761,10547154,0.99\n23,Walk On Water,5,1,1,\"Steven Tyler, Joe Perry, Jack Blades, Tommy Shaw\",295680,9719579,0.99\n24,Love In An Elevator,5,1,1,\"Steven Tyler, Joe Perry\",321828,10552051,0.99\n25,Rag Doll,5,1,1,\"Steven Tyler, Joe Perry, Jim Vallance, Holly Knight\",264698,8675345,0.99\n26,What It Takes,5,1,1,\"Steven Tyler, Joe Perry, Desmond Child\",310622,10144730,0.99\n27,Dude (Looks Like A Lady),5,1,1,\"Steven Tyler, Joe Perry, Desmond Child\",264855,8679940,0.99\n28,Janie's Got A Gun,5,1,1,\"Steven Tyler, Tom Hamilton\",330736,10869391,0.99\n29,Cryin',5,1,1,\"Steven Tyler, Joe Perry, Taylor Rhodes\",309263,10056995,0.99\n30,Amazing,5,1,1,\"Steven Tyler, Richie Supa\",356519,11616195,0.99\n31,Blind Man,5,1,1,\"Steven Tyler, Joe Perry, Taylor Rhodes\",240718,7877453,0.99\n32,Deuces Are Wild,5,1,1,\"Steven Tyler, Jim Vallance\",215875,7074167,0.99\n33,The Other Side,5,1,1,\"Steven Tyler, Jim Vallance\",244375,7983270,0.99\n34,Crazy,5,1,1,\"Steven Tyler, Joe Perry, Desmond Child\",316656,10402398,0.99\n35,Eat The Rich,5,1,1,\"Steven Tyler, Joe Perry, Jim Vallance\",251036,8262039,0.99\n36,Angel,5,1,1,\"Steven Tyler, Desmond Child\",307617,9989331,0.99\n37,Livin' On The Edge,5,1,1,\"Steven Tyler, Joe Perry, Mark Hudson\",381231,12374569,0.99\n38,All I Really Want,6,1,1,Alanis Morissette & Glenn Ballard,284891,9375567,0.99\n39,You Oughta Know,6,1,1,Alanis Morissette & Glenn Ballard,249234,8196916,0.99\n40,Perfect,6,1,1,Alanis Morissette & Glenn Ballard,188133,6145404,0.99\n41,Hand In My Pocket,6,1,1,Alanis Morissette & Glenn Ballard,221570,7224246,0.99\n42,Right Through You,6,1,1,Alanis Morissette & Glenn Ballard,176117,5793082,0.99\n43,Forgiven,6,1,1,Alanis Morissette & Glenn Ballard,300355,9753256,0.99\n44,You Learn,6,1,1,Alanis Morissette & Glenn Ballard,239699,7824837,0.99\n45,Head Over Feet,6,1,1,Alanis Morissette & Glenn Ballard,267493,8758008,0.99\n46,Mary Jane,6,1,1,Alanis Morissette & Glenn Ballard,280607,9163588,0.99\n47,Ironic,6,1,1,Alanis Morissette & Glenn Ballard,229825,7598866,0.99\n48,Not The Doctor,6,1,1,Alanis Morissette & Glenn Ballard,227631,7604601,0.99\n49,Wake Up,6,1,1,Alanis Morissette & Glenn Ballard,293485,9703359,0.99\n50,You Oughta Know (Alternate),6,1,1,Alanis Morissette & Glenn Ballard,491885,16008629,0.99\n51,We Die Young,7,1,1,Jerry Cantrell,152084,4925362,0.99\n52,Man In The Box,7,1,1,\"Jerry Cantrell, Layne Staley\",286641,9310272,0.99\n53,Sea Of Sorrow,7,1,1,Jerry Cantrell,349831,11316328,0.99\n54,Bleed The Freak,7,1,1,Jerry Cantrell,241946,7847716,0.99\n55,I Can't Remember,7,1,1,\"Jerry Cantrell, Layne Staley\",222955,7302550,0.99\n56,\"Love, Hate, Love\",7,1,1,\"Jerry Cantrell, Layne Staley\",387134,12575396,0.99\n57,It Ain't Like That,7,1,1,\"Jerry Cantrell, Michael Starr, Sean Kinney\",277577,8993793,0.99\n58,Sunshine,7,1,1,Jerry Cantrell,284969,9216057,0.99\n59,Put You Down,7,1,1,Jerry Cantrell,196231,6420530,0.99\n60,Confusion,7,1,1,\"Jerry Cantrell, Michael Starr, Layne Staley\",344163,11183647,0.99\n61,I Know Somethin (Bout You),7,1,1,Jerry Cantrell,261955,8497788,0.99\n62,Real Thing,7,1,1,\"Jerry Cantrell, Layne Staley\",243879,7937731,0.99\n63,Desafinado,8,1,2,,185338,5990473,0.99\n64,Garota De Ipanema,8,1,2,,285048,9348428,0.99\n65,Samba De Uma Nota Só (One Note Samba),8,1,2,,137273,4535401,0.99\n66,Por Causa De Você,8,1,2,,169900,5536496,0.99\n67,Ligia,8,1,2,,251977,8226934,0.99\n68,Fotografia,8,1,2,,129227,4198774,0.99\n69,Dindi (Dindi),8,1,2,,253178,8149148,0.99\n70,Se Todos Fossem Iguais A Você (Instrumental),8,1,2,,134948,4393377,0.99\n71,Falando De Amor,8,1,2,,219663,7121735,0.99\n72,Angela,8,1,2,,169508,5574957,0.99\n73,Corcovado (Quiet Nights Of Quiet Stars),8,1,2,,205662,6687994,0.99\n74,Outra Vez,8,1,2,,126511,4110053,0.99\n75,O Boto (Bôto),8,1,2,,366837,12089673,0.99\n76,\"Canta, Canta Mais\",8,1,2,,271856,8719426,0.99\n77,Enter Sandman,9,1,3,Apocalyptica,221701,7286305,0.99\n78,Master Of Puppets,9,1,3,Apocalyptica,436453,14375310,0.99\n79,Harvester Of Sorrow,9,1,3,Apocalyptica,374543,12372536,0.99\n80,The Unforgiven,9,1,3,Apocalyptica,322925,10422447,0.99\n81,Sad But True,9,1,3,Apocalyptica,288208,9405526,0.99\n82,Creeping Death,9,1,3,Apocalyptica,308035,10110980,0.99\n83,Wherever I May Roam,9,1,3,Apocalyptica,369345,12033110,0.99\n84,Welcome Home (Sanitarium),9,1,3,Apocalyptica,350197,11406431,0.99\n85,Cochise,10,1,1,Audioslave/Chris Cornell,222380,5339931,0.99\n86,Show Me How to Live,10,1,1,Audioslave/Chris Cornell,277890,6672176,0.99\n87,Gasoline,10,1,1,Audioslave/Chris Cornell,279457,6709793,0.99\n88,What You Are,10,1,1,Audioslave/Chris Cornell,249391,5988186,0.99\n89,Like a Stone,10,1,1,Audioslave/Chris Cornell,294034,7059624,0.99\n90,Set It Off,10,1,1,Audioslave/Chris Cornell,263262,6321091,0.99\n91,Shadow on the Sun,10,1,1,Audioslave/Chris Cornell,343457,8245793,0.99\n92,I am the Highway,10,1,1,Audioslave/Chris Cornell,334942,8041411,0.99\n93,Exploder,10,1,1,Audioslave/Chris Cornell,206053,4948095,0.99\n94,Hypnotize,10,1,1,Audioslave/Chris Cornell,206628,4961887,0.99\n95,Bring'em Back Alive,10,1,1,Audioslave/Chris Cornell,329534,7911634,0.99\n96,Light My Way,10,1,1,Audioslave/Chris Cornell,303595,7289084,0.99\n97,Getaway Car,10,1,1,Audioslave/Chris Cornell,299598,7193162,0.99\n98,The Last Remaining Light,10,1,1,Audioslave/Chris Cornell,317492,7622615,0.99\n99,Your Time Has Come,11,1,4,\"Cornell, Commerford, Morello, Wilk\",255529,8273592,0.99\n100,Out Of Exile,11,1,4,\"Cornell, Commerford, Morello, Wilk\",291291,9506571,0.99\n101,Be Yourself,11,1,4,\"Cornell, Commerford, Morello, Wilk\",279484,9106160,0.99\n102,Doesn't Remind Me,11,1,4,\"Cornell, Commerford, Morello, Wilk\",255869,8357387,0.99\n103,Drown Me Slowly,11,1,4,\"Cornell, Commerford, Morello, Wilk\",233691,7609178,0.99\n104,Heaven's Dead,11,1,4,\"Cornell, Commerford, Morello, Wilk\",276688,9006158,0.99\n105,The Worm,11,1,4,\"Cornell, Commerford, Morello, Wilk\",237714,7710800,0.99\n106,Man Or Animal,11,1,4,\"Cornell, Commerford, Morello, Wilk\",233195,7542942,0.99\n107,Yesterday To Tomorrow,11,1,4,\"Cornell, Commerford, Morello, Wilk\",273763,8944205,0.99\n108,Dandelion,11,1,4,\"Cornell, Commerford, Morello, Wilk\",278125,9003592,0.99\n109,#1 Zero,11,1,4,\"Cornell, Commerford, Morello, Wilk\",299102,9731988,0.99\n110,The Curse,11,1,4,\"Cornell, Commerford, Morello, Wilk\",309786,10029406,0.99\n111,Money,12,1,5,\"Berry Gordy, Jr./Janie Bradford\",147591,2365897,0.99\n112,Long Tall Sally,12,1,5,\"Enotris Johnson/Little Richard/Robert \"\"Bumps\"\" Blackwell\",106396,1707084,0.99\n113,Bad Boy,12,1,5,Larry Williams,116088,1862126,0.99\n114,Twist And Shout,12,1,5,Bert Russell/Phil Medley,161123,2582553,0.99\n115,Please Mr. Postman,12,1,5,Brian Holland/Freddie Gorman/Georgia Dobbins/Robert Bateman/William Garrett,137639,2206986,0.99\n116,C'Mon Everybody,12,1,5,Eddie Cochran/Jerry Capehart,140199,2247846,0.99\n117,Rock 'N' Roll Music,12,1,5,Chuck Berry,141923,2276788,0.99\n118,Slow Down,12,1,5,Larry Williams,163265,2616981,0.99\n119,Roadrunner,12,1,5,Bo Diddley,143595,2301989,0.99\n120,Carol,12,1,5,Chuck Berry,143830,2306019,0.99\n121,Good Golly Miss Molly,12,1,5,Little Richard,106266,1704918,0.99\n122,20 Flight Rock,12,1,5,Ned Fairchild,107807,1299960,0.99\n123,Quadrant,13,1,2,Billy Cobham,261851,8538199,0.99\n124,Snoopy's search-Red baron,13,1,2,Billy Cobham,456071,15075616,0.99\n125,\"Spanish moss-\"\"A sound portrait\"\"-Spanish moss\",13,1,2,Billy Cobham,248084,8217867,0.99\n126,Moon germs,13,1,2,Billy Cobham,294060,9714812,0.99\n127,Stratus,13,1,2,Billy Cobham,582086,19115680,0.99\n128,The pleasant pheasant,13,1,2,Billy Cobham,318066,10630578,0.99\n129,Solo-Panhandler,13,1,2,Billy Cobham,246151,8230661,0.99\n130,Do what cha wanna,13,1,2,George Duke,274155,9018565,0.99\n131,Intro/ Low Down,14,1,3,,323683,10642901,0.99\n132,13 Years Of Grief,14,1,3,,246987,8137421,0.99\n133,Stronger Than Death,14,1,3,,300747,9869647,0.99\n134,All For You,14,1,3,,235833,7726948,0.99\n135,Super Terrorizer,14,1,3,,319373,10513905,0.99\n136,Phoney Smile Fake Hellos,14,1,3,,273606,9011701,0.99\n137,Lost My Better Half,14,1,3,,284081,9355309,0.99\n138,Bored To Tears,14,1,3,,247327,8130090,0.99\n139,A.N.D.R.O.T.A.Z.,14,1,3,,266266,8574746,0.99\n140,Born To Booze,14,1,3,,282122,9257358,0.99\n141,World Of Trouble,14,1,3,,359157,11820932,0.99\n142,No More Tears,14,1,3,,555075,18041629,0.99\n143,The Begining... At Last,14,1,3,,365662,11965109,0.99\n144,Heart Of Gold,15,1,3,,194873,6417460,0.99\n145,Snowblind,15,1,3,,420022,13842549,0.99\n146,Like A Bird,15,1,3,,276532,9115657,0.99\n147,Blood In The Wall,15,1,3,,284368,9359475,0.99\n148,The Beginning...At Last,15,1,3,,271960,8975814,0.99\n149,Black Sabbath,16,1,3,,382066,12440200,0.99\n150,The Wizard,16,1,3,,264829,8646737,0.99\n151,Behind The Wall Of Sleep,16,1,3,,217573,7169049,0.99\n152,N.I.B.,16,1,3,,368770,12029390,0.99\n153,Evil Woman,16,1,3,,204930,6655170,0.99\n154,Sleeping Village,16,1,3,,644571,21128525,0.99\n155,Warning,16,1,3,,212062,6893363,0.99\n156,Wheels Of Confusion / The Straightener,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",494524,16065830,0.99\n157,Tomorrow's Dream,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",192496,6252071,0.99\n158,Changes,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",286275,9175517,0.99\n159,FX,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",103157,3331776,0.99\n160,Supernaut,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",285779,9245971,0.99\n161,Snowblind,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",331676,10813386,0.99\n162,Cornucopia,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",234814,7653880,0.99\n163,Laguna Sunrise,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",173087,5671374,0.99\n164,St. Vitus Dance,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",149655,4884969,0.99\n165,Under The Sun/Every Day Comes and Goes,17,1,3,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",350458,11360486,0.99\n166,Smoked Pork,18,1,4,,47333,1549074,0.99\n167,Body Count's In The House,18,1,4,,204251,6715413,0.99\n168,Now Sports,18,1,4,,4884,161266,0.99\n169,Body Count,18,1,4,,317936,10489139,0.99\n170,A Statistic,18,1,4,,6373,211997,0.99\n171,Bowels Of The Devil,18,1,4,,223216,7324125,0.99\n172,The Real Problem,18,1,4,,11650,387360,0.99\n173,KKK Bitch,18,1,4,,173008,5709631,0.99\n174,D Note,18,1,4,,95738,3067064,0.99\n175,Voodoo,18,1,4,,300721,9875962,0.99\n176,The Winner Loses,18,1,4,,392254,12843821,0.99\n177,There Goes The Neighborhood,18,1,4,,350171,11443471,0.99\n178,Oprah,18,1,4,,6635,224313,0.99\n179,Evil Dick,18,1,4,,239020,7828873,0.99\n180,Body Count Anthem,18,1,4,,166426,5463690,0.99\n181,Momma's Gotta Die Tonight,18,1,4,,371539,12122946,0.99\n182,Freedom Of Speech,18,1,4,,281234,9337917,0.99\n183,King In Crimson,19,1,3,Roy Z,283167,9218499,0.99\n184,Chemical Wedding,19,1,3,Roy Z,246177,8022764,0.99\n185,The Tower,19,1,3,Roy Z,285257,9435693,0.99\n186,Killing Floor,19,1,3,Adrian Smith,269557,8854240,0.99\n187,Book Of Thel,19,1,3,Eddie Casillas/Roy Z,494393,16034404,0.99\n188,Gates Of Urizen,19,1,3,Roy Z,265351,8627004,0.99\n189,Jerusalem,19,1,3,Roy Z,402390,13194463,0.99\n190,Trupets Of Jericho,19,1,3,Roy Z,359131,11820908,0.99\n191,Machine Men,19,1,3,Adrian Smith,341655,11138147,0.99\n192,The Alchemist,19,1,3,Roy Z,509413,16545657,0.99\n193,Realword,19,1,3,Roy Z,237531,7802095,0.99\n194,First Time I Met The Blues,20,1,6,Eurreal Montgomery,140434,4604995,0.99\n195,Let Me Love You Baby,20,1,6,Willie Dixon,175386,5716994,0.99\n196,Stone Crazy,20,1,6,Buddy Guy,433397,14184984,0.99\n197,Pretty Baby,20,1,6,Willie Dixon,237662,7848282,0.99\n198,When My Left Eye Jumps,20,1,6,Al Perkins/Willie Dixon,235311,7685363,0.99\n199,Leave My Girl Alone,20,1,6,Buddy Guy,204721,6859518,0.99\n200,She Suits Me To A Tee,20,1,6,Buddy Guy,136803,4456321,0.99\n201,Keep It To Myself (Aka Keep It To Yourself),20,1,6,Sonny Boy Williamson [I],166060,5487056,0.99\n202,My Time After Awhile,20,1,6,Robert Geddins/Ron Badger/Sheldon Feinberg,182491,6022698,0.99\n203,Too Many Ways (Alternate),20,1,6,Willie Dixon,135053,4459946,0.99\n204,Talkin' 'Bout Women Obviously,20,1,6,Amos Blakemore/Buddy Guy,589531,19161377,0.99\n205,Jorge Da Capadócia,21,1,7,Jorge Ben,177397,5842196,0.99\n206,Prenda Minha,21,1,7,Tradicional,99369,3225364,0.99\n207,Meditação,21,1,7,Tom Jobim - Newton Mendoça,148793,4865597,0.99\n208,Terra,21,1,7,Caetano Veloso,482429,15889054,0.99\n209,Eclipse Oculto,21,1,7,Caetano Veloso,221936,7382703,0.99\n210,\"Texto \"\"Verdade Tropical\"\"\",21,1,7,Caetano Veloso,84088,2752161,0.99\n211,Bem Devagar,21,1,7,Gilberto Gil,133172,4333651,0.99\n212,Drão,21,1,7,Gilberto Gil,156264,5065932,0.99\n213,Saudosismo,21,1,7,Caetano Veloso,144326,4726981,0.99\n214,Carolina,21,1,7,Chico Buarque,181812,5924159,0.99\n215,Sozinho,21,1,7,Peninha,190589,6253200,0.99\n216,Esse Cara,21,1,7,Caetano Veloso,223111,7217126,0.99\n217,Mel,21,1,7,Caetano Veloso - Waly Salomão,294765,9854062,0.99\n218,Linha Do Equador,21,1,7,Caetano Veloso - Djavan,299337,10003747,0.99\n219,Odara,21,1,7,Caetano Veloso,141270,4704104,0.99\n220,A Luz De Tieta,21,1,7,Caetano Veloso,251742,8507446,0.99\n221,Atrás Da Verd-E-Rosa Só Não Vai Quem Já Morreu,21,1,7,David Corrêa - Paulinho Carvalho - Carlos Sena - Bira do Ponto,307252,10364247,0.99\n222,Vida Boa,21,1,7,Fausto Nilo - Armandinho,281730,9411272,0.99\n223,Sozinho (Hitmakers Classic Mix),22,1,7,,436636,14462072,0.99\n224,Sozinho (Hitmakers Classic Radio Edit),22,1,7,,195004,6455134,0.99\n225,Sozinho (Caêdrum 'n' Bass),22,1,7,,328071,10975007,0.99\n226,Carolina,23,1,7,,163056,5375395,0.99\n227,Essa Moça Ta Diferente,23,1,7,,167235,5568574,0.99\n228,Vai Passar,23,1,7,,369763,12359161,0.99\n229,Samba De Orly,23,1,7,,162429,5431854,0.99\n230,\"Bye, Bye Brasil\",23,1,7,,283402,9499590,0.99\n231,Atras Da Porta,23,1,7,,189675,6132843,0.99\n232,Tatuagem,23,1,7,,172120,5645703,0.99\n233,O Que Será (À Flor Da Terra),23,1,7,,167288,5574848,0.99\n234,Morena De Angola,23,1,7,,186801,6373932,0.99\n235,Apesar De Você,23,1,7,,234501,7886937,0.99\n236,A Banda,23,1,7,,132493,4349539,0.99\n237,Minha Historia,23,1,7,,182256,6029673,0.99\n238,Com Açúcar E Com Afeto,23,1,7,,175386,5846442,0.99\n239,Brejo Da Cruz,23,1,7,,214099,7270749,0.99\n240,Meu Caro Amigo,23,1,7,,260257,8778172,0.99\n241,Geni E O Zepelim,23,1,7,,317570,10342226,0.99\n242,Trocando Em Miúdos,23,1,7,,169717,5461468,0.99\n243,Vai Trabalhar Vagabundo,23,1,7,,139154,4693941,0.99\n244,Gota D'água,23,1,7,,153208,5074189,0.99\n245,Construção / Deus Lhe Pague,23,1,7,,383059,12675305,0.99\n246,Mateus Enter,24,1,7,Chico Science,33149,1103013,0.99\n247,O Cidadão Do Mundo,24,1,7,Chico Science,200933,6724966,0.99\n248,Etnia,24,1,7,Chico Science,152555,5061413,0.99\n249,Quilombo Groove [Instrumental],24,1,7,Chico Science,151823,5042447,0.99\n250,Macô,24,1,7,Chico Science,249600,8253934,0.99\n251,Um Passeio No Mundo Livre,24,1,7,Chico Science,240091,7984291,0.99\n252,Samba Do Lado,24,1,7,Chico Science,227317,7541688,0.99\n253,Maracatu Atômico,24,1,7,Chico Science,284264,9670057,0.99\n254,O Encontro De Isaac Asimov Com Santos Dumont No Céu,24,1,7,Chico Science,99108,3240816,0.99\n255,Corpo De Lama,24,1,7,Chico Science,232672,7714954,0.99\n256,Sobremesa,24,1,7,Chico Science,240091,7960868,0.99\n257,Manguetown,24,1,7,Chico Science,194560,6475159,0.99\n258,Um Satélite Na Cabeça,24,1,7,Chico Science,126615,4272821,0.99\n259,Baião Ambiental [Instrumental],24,1,7,Chico Science,152659,5198539,0.99\n260,Sangue De Bairro,24,1,7,Chico Science,132231,4415557,0.99\n261,Enquanto O Mundo Explode,24,1,7,Chico Science,88764,2968650,0.99\n262,Interlude Zumbi,24,1,7,Chico Science,71627,2408550,0.99\n263,Criança De Domingo,24,1,7,Chico Science,208222,6984813,0.99\n264,Amor De Muito,24,1,7,Chico Science,175333,5881293,0.99\n265,Samidarish [Instrumental],24,1,7,Chico Science,272431,8911641,0.99\n266,Maracatu Atômico [Atomic Version],24,1,7,Chico Science,273084,9019677,0.99\n267,Maracatu Atômico [Ragga Mix],24,1,7,Chico Science,210155,6986421,0.99\n268,Maracatu Atômico [Trip Hop],24,1,7,Chico Science,221492,7380787,0.99\n269,Banditismo Por Uma Questa,25,1,7,,307095,10251097,0.99\n270,Banditismo Por Uma Questa,25,1,7,,243644,8147224,0.99\n271,Rios Pontes & Overdrives,25,1,7,,286720,9659152,0.99\n272,Cidade,25,1,7,,216346,7241817,0.99\n273,Praiera,25,1,7,,183640,6172781,0.99\n274,Samba Makossa,25,1,7,,271856,9095410,0.99\n275,Da Lama Ao Caos,25,1,7,,251559,8378065,0.99\n276,Maracatu De Tiro Certeiro,25,1,7,,88868,2901397,0.99\n277,Salustiano Song,25,1,7,,215405,7183969,0.99\n278,Antene Se,25,1,7,,248372,8253618,0.99\n279,Risoflora,25,1,7,,105586,3536938,0.99\n280,Lixo Do Mangue,25,1,7,,193253,6534200,0.99\n281,Computadores Fazem Arte,25,1,7,,404323,13702771,0.99\n282,Girassol,26,1,8,Bino Farias/Da Gama/Lazão/Pedro Luis/Toni Garrido,249808,8327676,0.99\n283,A Sombra Da Maldade,26,1,8,Da Gama/Toni Garrido,230922,7697230,0.99\n284,Johnny B. Goode,26,1,8,Chuck Berry,254615,8505985,0.99\n285,Soldado Da Paz,26,1,8,Herbert Vianna,194220,6455080,0.99\n286,Firmamento,26,1,8,Bino Farias/Da Gama/Henry Lawes/Lazão/Toni Garrido/Winston Foser-Vers,222145,7402658,0.99\n287,Extra,26,1,8,Gilberto Gil,304352,10078050,0.99\n288,O Erê,26,1,8,Bernardo Vilhena/Bino Farias/Da Gama/Lazão/Toni Garrido,236382,7866924,0.99\n289,Podes Crer,26,1,8,Bino Farias/Da Gama/Lazão/Toni Garrido,232280,7747747,0.99\n290,A Estrada,26,1,8,Bino Farias/Da Gama/Lazão/Toni Garrido,248842,8275673,0.99\n291,Berlim,26,1,8,Da Gama/Toni Garrido,207542,6920424,0.99\n292,Já Foi,26,1,8,Bino Farias/Da Gama/Lazão/Toni Garrido,221544,7388466,0.99\n293,Onde Você Mora?,26,1,8,Marisa Monte/Nando Reis,256026,8502588,0.99\n294,Pensamento,26,1,8,Bino Farias/Da Gamma/Lazão/Rás Bernard,173008,5748424,0.99\n295,Conciliação,26,1,8,Da Gama/Lazão/Rás Bernardo,257619,8552474,0.99\n296,Realidade Virtual,26,1,8,Bino Farias/Da Gama/Lazão/Toni Garrido,195239,6503533,0.99\n297,Mensagem,26,1,8,Bino Farias/Da Gama/Lazão/Rás Bernardo,225332,7488852,0.99\n298,A Cor Do Sol,26,1,8,Bernardo Vilhena/Da Gama/Lazão,231392,7663348,0.99\n299,Onde Você Mora?,27,1,8,Marisa Monte/Nando Reis,298396,10056970,0.99\n300,O Erê,27,1,8,Bernardo Vilhena/Bino/Da Gama/Lazao/Toni Garrido,206942,6950332,0.99\n301,A Sombra Da Maldade,27,1,8,Da Gama/Toni Garrido,285231,9544383,0.99\n302,A Estrada,27,1,8,Da Gama/Lazao/Toni Garrido,282174,9344477,0.99\n303,Falar A Verdade,27,1,8,Bino/Da Gama/Ras Bernardo,244950,8189093,0.99\n304,Firmamento,27,1,8,Harry Lawes/Winston Foster-Vers,225488,7507866,0.99\n305,Pensamento,27,1,8,Bino/Da Gama/Ras Bernardo,192391,6399761,0.99\n306,Realidade Virtual,27,1,8,Bino/Da Gamma/Lazao/Toni Garrido,240300,8069934,0.99\n307,Doutor,27,1,8,Bino/Da Gama/Toni Garrido,178155,5950952,0.99\n308,Na Frente Da TV,27,1,8,Bino/Da Gama/Lazao/Ras Bernardo,289750,9633659,0.99\n309,Downtown,27,1,8,Cidade Negra,239725,8024386,0.99\n310,Sábado A Noite,27,1,8,Lulu Santos,267363,8895073,0.99\n311,A Cor Do Sol,27,1,8,Bernardo Vilhena/Da Gama/Lazao,273031,9142937,0.99\n312,Eu Também Quero Beijar,27,1,8,Fausto Nilo/Moraes Moreira/Pepeu Gomes,211147,7029400,0.99\n313,Noite Do Prazer,28,1,7,,311353,10309980,0.99\n314,À Francesa,28,1,7,,244532,8150846,0.99\n315,Cada Um Cada Um (A Namoradeira),28,1,7,,253492,8441034,0.99\n316,Linha Do Equador,28,1,7,,244715,8123466,0.99\n317,Amor Demais,28,1,7,,254040,8420093,0.99\n318,Férias,28,1,7,,264202,8731945,0.99\n319,Gostava Tanto De Você,28,1,7,,230452,7685326,0.99\n320,Flor Do Futuro,28,1,7,,275748,9205941,0.99\n321,Felicidade Urgente,28,1,7,,266605,8873358,0.99\n322,Livre Pra Viver,28,1,7,,214595,7111596,0.99\n323,\"Dig-Dig, Lambe-Lambe (Ao Vivo)\",29,1,9,Cassiano Costa/Cintia Maviane/J.F./Lucas Costa,205479,6892516,0.99\n324,Pererê,29,1,9,Augusto Conceição/Chiclete Com Banana,198661,6643207,0.99\n325,TriboTchan,29,1,9,Cal Adan/Paulo Levi,194194,6507950,0.99\n326,\"Tapa Aqui, Descobre Ali\",29,1,9,Paulo Levi/W. Rangel,188630,6327391,0.99\n327,Daniela,29,1,9,Jorge Cardoso/Pierre Onasis,230791,7748006,0.99\n328,Bate Lata,29,1,9,Fábio Nolasco/Gal Sales/Ivan Brasil,206733,7034985,0.99\n329,Garotas do Brasil,29,1,9,\"Garay, Ricardo Engels/Luca Predabom/Ludwig, Carlos Henrique/Maurício Vieira\",210155,6973625,0.99\n330,Levada do Amor (Ailoviu),29,1,9,Luiz Wanderley/Paulo Levi,190093,6457752,0.99\n331,Lavadeira,29,1,9,\"Do Vale, Valverde/Gal Oliveira/Luciano Pinto\",214256,7254147,0.99\n332,Reboladeira,29,1,9,Cal Adan/Ferrugem/Julinho Carioca/Tríona Ní Dhomhnaill,210599,7027525,0.99\n333,É que Nessa Encarnação Eu Nasci Manga,29,1,9,Lucina/Luli,196519,6568081,0.99\n334,Reggae Tchan,29,1,9,\"Cal Adan/Del Rey, Tension/Edu Casanova\",206654,6931328,0.99\n335,My Love,29,1,9,Jauperi/Zeu Góes,203493,6772813,0.99\n336,Latinha de Cerveja,29,1,9,Adriano Bernandes/Edmar Neves,166687,5532564,0.99\n337,You Shook Me,30,1,1,J B Lenoir/Willie Dixon,315951,10249958,0.99\n338,I Can't Quit You Baby,30,1,1,Willie Dixon,263836,8581414,0.99\n339,Communication Breakdown,30,1,1,Jimmy Page/John Bonham/John Paul Jones,192653,6287257,0.99\n340,Dazed and Confused,30,1,1,Jimmy Page,401920,13035765,0.99\n341,The Girl I Love She Got Long Black Wavy Hair,30,1,1,Jimmy Page/John Bonham/John Estes/John Paul Jones/Robert Plant,183327,5995686,0.99\n342,What is and Should Never Be,30,1,1,Jimmy Page/Robert Plant,260675,8497116,0.99\n343,Communication Breakdown(2),30,1,1,Jimmy Page/John Bonham/John Paul Jones,161149,5261022,0.99\n344,Travelling Riverside Blues,30,1,1,Jimmy Page/Robert Johnson/Robert Plant,312032,10232581,0.99\n345,Whole Lotta Love,30,1,1,Jimmy Page/John Bonham/John Paul Jones/Robert Plant/Willie Dixon,373394,12258175,0.99\n346,Somethin' Else,30,1,1,Bob Cochran/Sharon Sheeley,127869,4165650,0.99\n347,Communication Breakdown(3),30,1,1,Jimmy Page/John Bonham/John Paul Jones,185260,6041133,0.99\n348,I Can't Quit You Baby(2),30,1,1,Willie Dixon,380551,12377615,0.99\n349,You Shook Me(2),30,1,1,J B Lenoir/Willie Dixon,619467,20138673,0.99\n350,How Many More Times,30,1,1,Chester Burnett/Jimmy Page/John Bonham/John Paul Jones/Robert Plant,711836,23092953,0.99\n351,Debra Kadabra,31,1,1,Frank Zappa,234553,7649679,0.99\n352,Carolina Hard-Core Ecstasy,31,1,1,Frank Zappa,359680,11731061,0.99\n353,Sam With The Showing Scalp Flat Top,31,1,1,Don Van Vliet,171284,5572993,0.99\n354,Poofter's Froth Wyoming Plans Ahead,31,1,1,Frank Zappa,183902,6007019,0.99\n355,200 Years Old,31,1,1,Frank Zappa,272561,8912465,0.99\n356,Cucamonga,31,1,1,Frank Zappa,144483,4728586,0.99\n357,Advance Romance,31,1,1,Frank Zappa,677694,22080051,0.99\n358,Man With The Woman Head,31,1,1,Don Van Vliet,88894,2922044,0.99\n359,Muffin Man,31,1,1,Frank Zappa,332878,10891682,0.99\n360,Vai-Vai 2001,32,1,10,,276349,9402241,0.99\n361,X-9 2001,32,1,10,,273920,9310370,0.99\n362,Gavioes 2001,32,1,10,,282723,9616640,0.99\n363,Nene 2001,32,1,10,,284969,9694508,0.99\n364,Rosas De Ouro 2001,32,1,10,,284342,9721084,0.99\n365,Mocidade Alegre 2001,32,1,10,,282488,9599937,0.99\n366,Camisa Verde 2001,32,1,10,,283454,9633755,0.99\n367,Leandro De Itaquera 2001,32,1,10,,274808,9451845,0.99\n368,Tucuruvi 2001,32,1,10,,287921,9883335,0.99\n369,Aguia De Ouro 2001,32,1,10,,284160,9698729,0.99\n370,Ipiranga 2001,32,1,10,,248293,8522591,0.99\n371,Morro Da Casa Verde 2001,32,1,10,,284708,9718778,0.99\n372,Perola Negra 2001,32,1,10,,281626,9619196,0.99\n373,Sao Lucas 2001,32,1,10,,296254,10020122,0.99\n374,Guanabara,33,1,7,Marcos Valle,247614,8499591,0.99\n375,Mas Que Nada,33,1,7,Jorge Ben,248398,8255254,0.99\n376,Vôo Sobre o Horizonte,33,1,7,J.r.Bertami/Parana,225097,7528825,0.99\n377,A Paz,33,1,7,Donato/Gilberto Gil,263183,8619173,0.99\n378,Wave (Vou te Contar),33,1,7,Antonio Carlos Jobim,271647,9057557,0.99\n379,Água de Beber,33,1,7,Antonio Carlos Jobim/Vinicius de Moraes,146677,4866476,0.99\n380,Samba da Bençaco,33,1,7,Baden Powell/Vinicius de Moraes,282200,9440676,0.99\n381,Pode Parar,33,1,7,Jorge Vercilo/Jota Maranhao,179408,6046678,0.99\n382,Menino do Rio,33,1,7,Caetano Veloso,262713,8737489,0.99\n383,Ando Meio Desligado,33,1,7,Caetano Veloso,195813,6547648,0.99\n384,Mistério da Raça,33,1,7,Luiz Melodia/Ricardo Augusto,184320,6191752,0.99\n385,All Star,33,1,7,Nando Reis,176326,5891697,0.99\n386,Menina Bonita,33,1,7,Alexandre Brazil/Pedro Luis/Rodrigo Cabelo,237087,7938246,0.99\n387,Pescador de Ilusões,33,1,7,Macelo Yuka/O Rappa,245524,8267067,0.99\n388,À Vontade (Live Mix),33,1,7,Bombom/Ed Motta,180636,5972430,0.99\n389,Maria Fumaça,33,1,7,Luiz Carlos/Oberdan,141008,4743149,0.99\n390,Sambassim (dj patife remix),33,1,7,Alba Carvalho/Fernando Porto,213655,7243166,0.99\n391,Garota De Ipanema,34,1,7,Vários,279536,9141343,0.99\n392,Tim Tim Por Tim Tim,34,1,7,Vários,213237,7143328,0.99\n393,Tarde Em Itapoã,34,1,7,Vários,313704,10344491,0.99\n394,Tanto Tempo,34,1,7,Vários,170292,5572240,0.99\n395,Eu Vim Da Bahia - Live,34,1,7,Vários,157988,5115428,0.99\n396,Alô Alô Marciano,34,1,7,Vários,238106,8013065,0.99\n397,Linha Do Horizonte,34,1,7,Vários,279484,9275929,0.99\n398,Only A Dream In Rio,34,1,7,Vários,371356,12192989,0.99\n399,Abrir A Porta,34,1,7,Vários,271960,8991141,0.99\n400,Alice,34,1,7,Vários,165982,5594341,0.99\n401,Momentos Que Marcam,34,1,7,Vários,280137,9313740,0.99\n402,Um Jantar Pra Dois,34,1,7,Vários,237714,7819755,0.99\n403,Bumbo Da Mangueira,34,1,7,Vários,270158,9073350,0.99\n404,Mr Funk Samba,34,1,7,Vários,213890,7102545,0.99\n405,Santo Antonio,34,1,7,Vários,162716,5492069,0.99\n406,Por Você,34,1,7,Vários,205557,6792493,0.99\n407,Só Tinha De Ser Com Você,34,1,7,Vários,389642,13085596,0.99\n408,Free Speech For The Dumb,35,1,3,Molaney/Morris/Roberts/Wainwright,155428,5076048,0.99\n409,It's Electric,35,1,3,Harris/Tatler,213995,6978601,0.99\n410,Sabbra Cadabra,35,1,3,Black Sabbath,380342,12418147,0.99\n411,Turn The Page,35,1,3,Seger,366524,11946327,0.99\n412,Die Die My Darling,35,1,3,Danzig,149315,4867667,0.99\n413,Loverman,35,1,3,Cave,472764,15446975,0.99\n414,Mercyful Fate,35,1,3,Diamond/Shermann,671712,21942829,0.99\n415,Astronomy,35,1,3,A.Bouchard/J.Bouchard/S.Pearlman,397531,13065612,0.99\n416,Whiskey In The Jar,35,1,3,Traditional,305005,9943129,0.99\n417,Tuesday's Gone,35,1,3,Collins/Van Zandt,545750,17900787,0.99\n418,The More I See,35,1,3,Molaney/Morris/Roberts/Wainwright,287973,9378873,0.99\n419,A Kind Of Magic,36,1,1,Roger Taylor,262608,8689618,0.99\n420,Under Pressure,36,1,1,Queen & David Bowie,236617,7739042,0.99\n421,Radio GA GA,36,1,1,Roger Taylor,343745,11358573,0.99\n422,I Want It All,36,1,1,Queen,241684,7876564,0.99\n423,I Want To Break Free,36,1,1,John Deacon,259108,8552861,0.99\n424,Innuendo,36,1,1,Queen,387761,12664591,0.99\n425,It's A Hard Life,36,1,1,Freddie Mercury,249417,8112242,0.99\n426,Breakthru,36,1,1,Queen,249234,8150479,0.99\n427,Who Wants To Live Forever,36,1,1,Brian May,297691,9577577,0.99\n428,Headlong,36,1,1,Queen,273057,8921404,0.99\n429,The Miracle,36,1,1,Queen,294974,9671923,0.99\n430,I'm Going Slightly Mad,36,1,1,Queen,248032,8192339,0.99\n431,The Invisible Man,36,1,1,Queen,238994,7920353,0.99\n432,Hammer To Fall,36,1,1,Brian May,220316,7255404,0.99\n433,Friends Will Be Friends,36,1,1,Freddie Mercury & John Deacon,248920,8114582,0.99\n434,The Show Must Go On,36,1,1,Queen,263784,8526760,0.99\n435,One Vision,36,1,1,Queen,242599,7936928,0.99\n436,Detroit Rock City,37,1,1,\"Paul Stanley, B. Ezrin\",218880,7146372,0.99\n437,Black Diamond,37,1,1,Paul Stanley,314148,10266007,0.99\n438,Hard Luck Woman,37,1,1,Paul Stanley,216032,7109267,0.99\n439,Sure Know Something,37,1,1,\"Paul Stanley, Vincent Poncia\",242468,7939886,0.99\n440,Love Gun,37,1,1,Paul Stanley,196257,6424915,0.99\n441,Deuce,37,1,1,Gene Simmons,185077,6097210,0.99\n442,Goin' Blind,37,1,1,\"Gene Simmons, S. Coronel\",216215,7045314,0.99\n443,Shock Me,37,1,1,Ace Frehley,227291,7529336,0.99\n444,Do You Love Me,37,1,1,\"Paul Stanley, B. Ezrin, K. Fowley\",214987,6976194,0.99\n445,She,37,1,1,\"Gene Simmons, S. Coronel\",248346,8229734,0.99\n446,I Was Made For Loving You,37,1,1,\"Paul Stanley, Vincent Poncia, Desmond Child\",271360,9018078,0.99\n447,Shout It Out Loud,37,1,1,\"Paul Stanley, Gene Simmons, B. Ezrin\",219742,7194424,0.99\n448,God Of Thunder,37,1,1,Paul Stanley,255791,8309077,0.99\n449,Calling Dr. Love,37,1,1,Gene Simmons,225332,7395034,0.99\n450,Beth,37,1,1,\"S. Penridge, Bob Ezrin, Peter Criss\",166974,5360574,0.99\n451,Strutter,37,1,1,\"Paul Stanley, Gene Simmons\",192496,6317021,0.99\n452,Rock And Roll All Nite,37,1,1,\"Paul Stanley, Gene Simmons\",173609,5735902,0.99\n453,Cold Gin,37,1,1,Ace Frehley,262243,8609783,0.99\n454,Plaster Caster,37,1,1,Gene Simmons,207333,6801116,0.99\n455,God Gave Rock 'n' Roll To You,37,1,1,\"Paul Stanley, Gene Simmons, Rus Ballard, Bob Ezrin\",320444,10441590,0.99\n456,Heart of the Night,38,1,2,,273737,9098263,0.99\n457,De La Luz,38,1,2,,315219,10518284,0.99\n458,Westwood Moon,38,1,2,,295627,9765802,0.99\n459,Midnight,38,1,2,,266866,8851060,0.99\n460,Playtime,38,1,2,,273580,9070880,0.99\n461,Surrender,38,1,2,,287634,9422926,0.99\n462,Valentino's,38,1,2,,296124,9848545,0.99\n463,Believe,38,1,2,,310778,10317185,0.99\n464,As We Sleep,38,1,2,,316865,10429398,0.99\n465,When Evening Falls,38,1,2,,298135,9863942,0.99\n466,J Squared,38,1,2,,288757,9480777,0.99\n467,Best Thing,38,1,2,,274259,9069394,0.99\n468,Maria,39,1,4,Billie Joe Armstrong -Words Green Day -Music,167262,5484747,0.99\n469,Poprocks And Coke,39,1,4,Billie Joe Armstrong -Words Green Day -Music,158354,5243078,0.99\n470,Longview,39,1,4,Billie Joe Armstrong -Words Green Day -Music,234083,7714939,0.99\n471,Welcome To Paradise,39,1,4,Billie Joe Armstrong -Words Green Day -Music,224208,7406008,0.99\n472,Basket Case,39,1,4,Billie Joe Armstrong -Words Green Day -Music,181629,5951736,0.99\n473,When I Come Around,39,1,4,Billie Joe Armstrong -Words Green Day -Music,178364,5839426,0.99\n474,She,39,1,4,Billie Joe Armstrong -Words Green Day -Music,134164,4425128,0.99\n475,J.A.R. (Jason Andrew Relva),39,1,4,Mike Dirnt -Words Green Day -Music,170997,5645755,0.99\n476,Geek Stink Breath,39,1,4,Billie Joe Armstrong -Words Green Day -Music,135888,4408983,0.99\n477,Brain Stew,39,1,4,Billie Joe Armstrong -Words Green Day -Music,193149,6305550,0.99\n478,Jaded,39,1,4,Billie Joe Armstrong -Words Green Day -Music,90331,2950224,0.99\n479,Walking Contradiction,39,1,4,Billie Joe Armstrong -Words Green Day -Music,151170,4932366,0.99\n480,Stuck With Me,39,1,4,Billie Joe Armstrong -Words Green Day -Music,135523,4431357,0.99\n481,Hitchin' A Ride,39,1,4,Billie Joe Armstrong -Words Green Day -Music,171546,5616891,0.99\n482,Good Riddance (Time Of Your Life),39,1,4,Billie Joe Armstrong -Words Green Day -Music,153600,5075241,0.99\n483,Redundant,39,1,4,Billie Joe Armstrong -Words Green Day -Music,198164,6481753,0.99\n484,Nice Guys Finish Last,39,1,4,Billie Joe Armstrong -Words Green Day -Music,170187,5604618,0.99\n485,Minority,39,1,4,Billie Joe Armstrong -Words Green Day -Music,168803,5535061,0.99\n486,Warning,39,1,4,Billie Joe Armstrong -Words Green Day -Music,221910,7343176,0.99\n487,Waiting,39,1,4,Billie Joe Armstrong -Words Green Day -Music,192757,6316430,0.99\n488,Macy's Day Parade,39,1,4,Billie Joe Armstrong -Words Green Day -Music,213420,7075573,0.99\n489,Into The Light,40,1,1,David Coverdale,76303,2452653,0.99\n490,River Song,40,1,1,David Coverdale,439510,14359478,0.99\n491,She Give Me ...,40,1,1,David Coverdale,252551,8385478,0.99\n492,Don't You Cry,40,1,1,David Coverdale,347036,11269612,0.99\n493,Love Is Blind,40,1,1,David Coverdale/Earl Slick,344999,11409720,0.99\n494,Slave,40,1,1,David Coverdale/Earl Slick,291892,9425200,0.99\n495,Cry For Love,40,1,1,Bossi/David Coverdale/Earl Slick,293015,9567075,0.99\n496,Living On Love,40,1,1,Bossi/David Coverdale/Earl Slick,391549,12785876,0.99\n497,Midnight Blue,40,1,1,David Coverdale/Earl Slick,298631,9750990,0.99\n498,Too Many Tears,40,1,1,Adrian Vanderberg/David Coverdale,359497,11810238,0.99\n499,Don't Lie To Me,40,1,1,David Coverdale/Earl Slick,283585,9288007,0.99\n500,Wherever You May Go,40,1,1,David Coverdale,239699,7803074,0.99\n501,Grito De Alerta,41,1,7,Gonzaga Jr.,202213,6539422,0.99\n502,Não Dá Mais Pra Segurar (Explode Coração),41,1,7,,219768,7083012,0.99\n503,Começaria Tudo Outra Vez,41,1,7,,196545,6473395,0.99\n504,O Que É O Que É ?,41,1,7,,259291,8650647,0.99\n505,Sangrando,41,1,7,Gonzaga Jr/Gonzaguinha,169717,5494406,0.99\n506,\"Diga Lá, Coração\",41,1,7,,255921,8280636,0.99\n507,Lindo Lago Do Amor,41,1,7,Gonzaga Jr.,249678,8353191,0.99\n508,Eu Apenas Queria Que Voçê Soubesse,41,1,7,,155637,5130056,0.99\n509,Com A Perna No Mundo,41,1,7,Gonzaga Jr.,227448,7747108,0.99\n510,E Vamos À Luta,41,1,7,,222406,7585112,0.99\n511,Um Homem Também Chora (Guerreiro Menino),41,1,7,,207229,6854219,0.99\n512,Comportamento Geral,41,1,7,Gonzaga Jr,181577,5997444,0.99\n513,Ponto De Interrogação,41,1,7,,180950,5946265,0.99\n514,\"Espere Por Mim, Morena\",41,1,7,Gonzaguinha,207072,6796523,0.99\n515,Meia-Lua Inteira,23,1,7,,222093,7466288,0.99\n516,Voce e Linda,23,1,7,,242938,8050268,0.99\n517,Um Indio,23,1,7,,195944,6453213,0.99\n518,Podres Poderes,23,1,7,,259761,8622495,0.99\n519,Voce Nao Entende Nada - Cotidiano,23,1,7,,421982,13885612,0.99\n520,O Estrangeiro,23,1,7,,374700,12472890,0.99\n521,Menino Do Rio,23,1,7,,147670,4862277,0.99\n522,Qualquer Coisa,23,1,7,,193410,6372433,0.99\n523,Sampa,23,1,7,,185051,6151831,0.99\n524,Queixa,23,1,7,,299676,9953962,0.99\n525,O Leaozinho,23,1,7,,184398,6098150,0.99\n526,Fora Da Ordem,23,1,7,,354011,11746781,0.99\n527,Terra,23,1,7,,401319,13224055,0.99\n528,\"Alegria, Alegria\",23,1,7,,169221,5497025,0.99\n529,Balada Do Louco,42,1,4,Arnaldo Baptista - Rita Lee,241057,7852328,0.99\n530,Ando Meio Desligado,42,1,4,Arnaldo Baptista - Rita Lee -  Sérgio Dias,287817,9484504,0.99\n531,Top Top,42,1,4,Os Mutantes - Arnolpho Lima Filho,146938,4875374,0.99\n532,Baby,42,1,4,Caetano Veloso,177188,5798202,0.99\n533,A E O Z,42,1,4,Mutantes,518556,16873005,0.99\n534,Panis Et Circenses,42,1,4,Caetano Veloso - Gilberto Gil,125152,4069688,0.99\n535,Chão De Estrelas,42,1,4,Orestes Barbosa-Sílvio Caldas,284813,9433620,0.99\n536,Vida De Cachorro,42,1,4,Rita Lee - Arnaldo Baptista - Sérgio Baptista,195186,6411149,0.99\n537,Bat Macumba,42,1,4,Gilberto Gil - Caetano Veloso,187794,6295223,0.99\n538,Desculpe Babe,42,1,4,Arnaldo Baptista - Rita Lee,170422,5637959,0.99\n539,Rita Lee,42,1,4,Arnaldo Baptista/Rita Lee/Sérgio Dias,189257,6270503,0.99\n540,\"Posso Perder Minha Mulher, Minha Mãe, Desde Que Eu Tenha O Rock And Roll\",42,1,4,Arnaldo Baptista - Rita Lee - Arnolpho Lima Filho,222955,7346254,0.99\n541,Banho De Lua,42,1,4,B. de Filippi - F. Migliaci - Versão: Fred Jorge,221831,7232123,0.99\n542,Meu Refrigerador Não Funciona,42,1,4,Arnaldo Baptista - Rita Lee - Sérgio Dias,382981,12495906,0.99\n543,Burn,43,1,1,Coverdale/Lord/Paice,453955,14775708,0.99\n544,Stormbringer,43,1,1,Coverdale,277133,9050022,0.99\n545,Gypsy,43,1,1,Coverdale/Hughes/Lord/Paice,339173,11046952,0.99\n546,Lady Double Dealer,43,1,1,Coverdale,233586,7608759,0.99\n547,Mistreated,43,1,1,Coverdale,758648,24596235,0.99\n548,Smoke On The Water,43,1,1,Gillan/Glover/Lord/Paice,618031,20103125,0.99\n549,You Fool No One,43,1,1,Coverdale/Lord/Paice,804101,26369966,0.99\n550,Custard Pie,44,1,1,Jimmy Page/Robert Plant,253962,8348257,0.99\n551,The Rover,44,1,1,Jimmy Page/Robert Plant,337084,11011286,0.99\n552,In My Time Of Dying,44,1,1,John Bonham/John Paul Jones,666017,21676727,0.99\n553,Houses Of The Holy,44,1,1,Jimmy Page/Robert Plant,242494,7972503,0.99\n554,Trampled Under Foot,44,1,1,John Paul Jones,336692,11154468,0.99\n555,Kashmir,44,1,1,John Bonham,508604,16686580,0.99\n556,Imperatriz,45,1,7,Guga/Marquinho Lessa/Tuninho Professor,339173,11348710,0.99\n557,Beija-Flor,45,1,7,Caruso/Cleber/Deo/Osmar,327000,10991159,0.99\n558,Viradouro,45,1,7,Dadinho/Gilbreto Gomes/Gustavo/P.C. Portugal/R. Mocoto,344320,11484362,0.99\n559,Mocidade,45,1,7,\"Domenil/J. Brito/Joaozinho/Rap, Marcelo Do\",261720,8817757,0.99\n560,Unidos Da Tijuca,45,1,7,\"Douglas/Neves, Vicente Das/Silva, Gilmar L./Toninho Gentil/Wantuir\",338834,11440689,0.99\n561,Salgueiro,45,1,7,\"Augusto/Craig Negoescu/Rocco Filho/Saara, Ze Carlos Da\",305920,10294741,0.99\n562,Mangueira,45,1,7,Bizuca/Clóvis Pê/Gilson Bernini/Marelo D'Aguia,298318,9999506,0.99\n563,União Da Ilha,45,1,7,\"Dito/Djalma Falcao/Ilha, Almir Da/Márcio André\",330945,11100945,0.99\n564,Grande Rio,45,1,7,Carlos Santos/Ciro/Claudio Russo/Zé Luiz,307252,10251428,0.99\n565,Portela,45,1,7,Flavio Bororo/Paulo Apparicio/Wagner Alves/Zeca Sereno,319608,10712216,0.99\n566,Caprichosos,45,1,7,Gule/Jorge 101/Lequinho/Luiz Piao,351320,11870956,0.99\n567,Tradição,45,1,7,Adalto Magalha/Lourenco,269165,9114880,0.99\n568,Império Serrano,45,1,7,Arlindo Cruz/Carlos Sena/Elmo Caetano/Mauricao,334942,11161196,0.99\n569,Tuiuti,45,1,7,\"Claudio Martins/David Lima/Kleber Rodrigues/Livre, Cesare Som\",259657,8749492,0.99\n570,(Da Le) Yaleo,46,1,1,Santana,353488,11769507,0.99\n571,Love Of My Life,46,1,1,Carlos Santana & Dave Matthews,347820,11634337,0.99\n572,Put Your Lights On,46,1,1,E. Shrody,285178,9394769,0.99\n573,Africa Bamba,46,1,1,\"I. Toure, S. Tidiane Toure, Carlos Santana & K. Perazzo\",282827,9492487,0.99\n574,Smooth,46,1,1,M. Itaal Shur & Rob Thomas,298161,9867455,0.99\n575,Do You Like The Way,46,1,1,L. Hill,354899,11741062,0.99\n576,Maria Maria,46,1,1,\"W. Jean, J. Duplessis, Carlos Santana, K. Perazzo & R. Rekow\",262635,8664601,0.99\n577,Migra,46,1,1,\"R. Taha, Carlos Santana & T. Lindsay\",329064,10963305,0.99\n578,Corazon Espinado,46,1,1,F. Olivera,276114,9206802,0.99\n579,Wishing It Was,46,1,1,\"Eale-Eye Cherry, M. Simpson, J. King & M. Nishita\",292832,9771348,0.99\n580,El Farol,46,1,1,Carlos Santana & KC Porter,291160,9599353,0.99\n581,Primavera,46,1,1,KC Porter & JB Eckl,378618,12504234,0.99\n582,The Calling,46,1,1,Carlos Santana & C. Thompson,747755,24703884,0.99\n583,Solução,47,1,7,,247431,8100449,0.99\n584,Manuel,47,1,7,,230269,7677671,0.99\n585,Entre E Ouça,47,1,7,,286302,9391004,0.99\n586,Um Contrato Com Deus,47,1,7,,202501,6636465,0.99\n587,Um Jantar Pra Dois,47,1,7,,244009,8021589,0.99\n588,Vamos Dançar,47,1,7,,226194,7617432,0.99\n589,Um Love,47,1,7,,181603,6095524,0.99\n590,Seis Da Tarde,47,1,7,,238445,7935898,0.99\n591,Baixo Rio,47,1,7,,198008,6521676,0.99\n592,Sombras Do Meu Destino,47,1,7,,280685,9161539,0.99\n593,Do You Have Other Loves?,47,1,7,,295235,9604273,0.99\n594,Agora Que O Dia Acordou,47,1,7,,323213,10572752,0.99\n595,Já!!!,47,1,7,,217782,7103608,0.99\n596,A Rua,47,1,7,,238027,7930264,0.99\n597,Now's The Time,48,1,2,Miles Davis,197459,6358868,0.99\n598,Jeru,48,1,2,Miles Davis,193410,6222536,0.99\n599,Compulsion,48,1,2,Miles Davis,345025,11254474,0.99\n600,Tempus Fugit,48,1,2,Miles Davis,231784,7548434,0.99\n601,Walkin',48,1,2,Miles Davis,807392,26411634,0.99\n602,'Round Midnight,48,1,2,Miles Davis,357459,11590284,0.99\n603,Bye Bye Blackbird,48,1,2,Miles Davis,476003,15549224,0.99\n604,New Rhumba,48,1,2,Miles Davis,277968,9018024,0.99\n605,Generique,48,1,2,Miles Davis,168777,5437017,0.99\n606,Summertime,48,1,2,Miles Davis,200437,6461370,0.99\n607,So What,48,1,2,Miles Davis,564009,18360449,0.99\n608,The Pan Piper,48,1,2,Miles Davis,233769,7593713,0.99\n609,Someday My Prince Will Come,48,1,2,Miles Davis,544078,17890773,0.99\n610,My Funny Valentine (Live),49,1,2,Miles Davis,907520,29416781,0.99\n611,E.S.P.,49,1,2,Miles Davis,330684,11079866,0.99\n612,Nefertiti,49,1,2,Miles Davis,473495,15478450,0.99\n613,Petits Machins (Little Stuff),49,1,2,Miles Davis,487392,16131272,0.99\n614,Miles Runs The Voodoo Down,49,1,2,Miles Davis,843964,27967919,0.99\n615,Little Church (Live),49,1,2,Miles Davis,196101,6273225,0.99\n616,Black Satin,49,1,2,Miles Davis,316682,10529483,0.99\n617,Jean Pierre (Live),49,1,2,Miles Davis,243461,7955114,0.99\n618,Time After Time,49,1,2,Miles Davis,220734,7292197,0.99\n619,Portia,49,1,2,Miles Davis,378775,12520126,0.99\n620,Space Truckin',50,1,1,Blackmore/Gillan/Glover/Lord/Paice,1196094,39267613,0.99\n621,Going Down / Highway Star,50,1,1,Gillan/Glover/Lord/Nix - Blackmore/Paice,913658,29846063,0.99\n622,Mistreated (Alternate Version),50,1,1,Blackmore/Coverdale,854700,27775442,0.99\n623,You Fool No One (Alternate Version),50,1,1,Blackmore/Coverdale/Lord/Paice,763924,24887209,0.99\n624,Jeepers Creepers,51,1,2,,185965,5991903,0.99\n625,Blue Rythm Fantasy,51,1,2,,348212,11204006,0.99\n626,Drum Boogie,51,1,2,,191555,6185636,0.99\n627,Let Me Off Uptown,51,1,2,,187637,6034685,0.99\n628,Leave Us Leap,51,1,2,,182726,5898810,0.99\n629,Opus No.1,51,1,2,,179800,5846041,0.99\n630,Boogie Blues,51,1,2,,204199,6603153,0.99\n631,How High The Moon,51,1,2,,201430,6529487,0.99\n632,Disc Jockey Jump,51,1,2,,193149,6260820,0.99\n633,Up An' Atom,51,1,2,,179565,5822645,0.99\n634,Bop Boogie,51,1,2,,189596,6093124,0.99\n635,Lemon Drop,51,1,2,,194089,6287531,0.99\n636,Coronation Drop,51,1,2,,176222,5899898,0.99\n637,Overtime,51,1,2,,163030,5432236,0.99\n638,Imagination,51,1,2,,289306,9444385,0.99\n639,Don't Take Your Love From Me,51,1,2,,282331,9244238,0.99\n640,Midget,51,1,2,,217025,7257663,0.99\n641,I'm Coming Virginia,51,1,2,,280163,9209827,0.99\n642,Payin' Them Dues Blues,51,1,2,,198556,6536918,0.99\n643,Jungle Drums,51,1,2,,199627,6546063,0.99\n644,Showcase,51,1,2,,201560,6697510,0.99\n645,Swedish Schnapps,51,1,2,,191268,6359750,0.99\n646,Samba Da Bênção,52,1,11,,409965,13490008,0.99\n647,Pot-Pourri N.º 4,52,1,11,,392437,13125975,0.99\n648,Onde Anda Você,52,1,11,,168437,5550356,0.99\n649,Samba Da Volta,52,1,11,,170631,5676090,0.99\n650,Canto De Ossanha,52,1,11,,204956,6771624,0.99\n651,Pot-Pourri N.º 5,52,1,11,,219898,7117769,0.99\n652,Formosa,52,1,11,,137482,4560873,0.99\n653,Como É Duro Trabalhar,52,1,11,,226168,7541177,0.99\n654,Minha Namorada,52,1,11,,244297,7927967,0.99\n655,Por Que Será,52,1,11,,162142,5371483,0.99\n656,Berimbau,52,1,11,,190667,6335548,0.99\n657,Deixa,52,1,11,,179826,5932799,0.99\n658,Pot-Pourri N.º 2,52,1,11,,211748,6878359,0.99\n659,Samba Em Prelúdio,52,1,11,,212636,6923473,0.99\n660,Carta Ao Tom 74,52,1,11,,162560,5382354,0.99\n661,Linha de Passe (João Bosco),53,1,7,,230948,7902328,0.99\n662,Pela Luz dos Olhos Teus (Miúcha e Tom Jobim),53,1,7,,163970,5399626,0.99\n663,Chão de Giz (Elba Ramalho),53,1,7,,274834,9016916,0.99\n664,Marina (Dorival Caymmi),53,1,7,,172643,5523628,0.99\n665,Aquarela (Toquinho),53,1,7,,259944,8480140,0.99\n666,Coração do Agreste (Fafá de Belém),53,1,7,,258194,8380320,0.99\n667,Dona (Roupa Nova),53,1,7,,243356,7991295,0.99\n668,Começaria Tudo Outra Vez (Maria Creuza),53,1,7,,206994,6851151,0.99\n669,Caçador de Mim (Sá & Guarabyra),53,1,7,,238341,7751360,0.99\n670,Romaria (Renato Teixeira),53,1,7,,244793,8033885,0.99\n671,As Rosas Não Falam (Beth Carvalho),53,1,7,,116767,3836641,0.99\n672,Wave (Os Cariocas),53,1,7,,130063,4298006,0.99\n673,Garota de Ipanema (Dick Farney),53,1,7,,174367,5767474,0.99\n674,Preciso Apender a Viver Só (Maysa),53,1,7,,143464,4642359,0.99\n675,Susie Q,54,1,1,Hawkins-Lewis-Broadwater,275565,9043825,0.99\n676,I Put A Spell On You,54,1,1,Jay Hawkins,272091,8943000,0.99\n677,Proud Mary,54,1,1,J. C. Fogerty,189022,6229590,0.99\n678,Bad Moon Rising,54,1,1,J. C. Fogerty,140146,4609835,0.99\n679,Lodi,54,1,1,J. C. Fogerty,191451,6260214,0.99\n680,Green River,54,1,1,J. C. Fogerty,154279,5105874,0.99\n681,Commotion,54,1,1,J. C. Fogerty,162899,5354252,0.99\n682,Down On The Corner,54,1,1,J. C. Fogerty,164858,5521804,0.99\n683,Fortunate Son,54,1,1,J. C. Fogerty,140329,4617559,0.99\n684,Travelin' Band,54,1,1,J. C. Fogerty,129358,4270414,0.99\n685,Who'll Stop The Rain,54,1,1,J. C. Fogerty,149394,4899579,0.99\n686,Up Around The Bend,54,1,1,J. C. Fogerty,162429,5368701,0.99\n687,Run Through The Jungle,54,1,1,J. C. Fogerty,186044,6156567,0.99\n688,Lookin' Out My Back Door,54,1,1,J. C. Fogerty,152946,5034670,0.99\n689,Long As I Can See The Light,54,1,1,J. C. Fogerty,213237,6924024,0.99\n690,I Heard It Through The Grapevine,54,1,1,Whitfield-Strong,664894,21947845,0.99\n691,Have You Ever Seen The Rain?,54,1,1,J. C. Fogerty,160052,5263675,0.99\n692,Hey Tonight,54,1,1,J. C. Fogerty,162847,5343807,0.99\n693,Sweet Hitch-Hiker,54,1,1,J. C. Fogerty,175490,5716603,0.99\n694,Someday Never Comes,54,1,1,J. C. Fogerty,239360,7945235,0.99\n695,Walking On The Water,55,1,1,J.C. Fogerty,281286,9302129,0.99\n696,\"Suzie-Q, Pt. 2\",55,1,1,J.C. Fogerty,244114,7986637,0.99\n697,Born On The Bayou,55,1,1,J.C. Fogerty,316630,10361866,0.99\n698,Good Golly Miss Molly,55,1,1,J.C. Fogerty,163604,5348175,0.99\n699,Tombstone Shadow,55,1,1,J.C. Fogerty,218880,7209080,0.99\n700,Wrote A Song For Everyone,55,1,1,J.C. Fogerty,296385,9675875,0.99\n701,Night Time Is The Right Time,55,1,1,J.C. Fogerty,190119,6211173,0.99\n702,Cotton Fields,55,1,1,J.C. Fogerty,178181,5919224,0.99\n703,It Came Out Of The Sky,55,1,1,J.C. Fogerty,176718,5807474,0.99\n704,Don't Look Now,55,1,1,J.C. Fogerty,131918,4366455,0.99\n705,The Midnight Special,55,1,1,J.C. Fogerty,253596,8297482,0.99\n706,Before You Accuse Me,55,1,1,J.C. Fogerty,207804,6815126,0.99\n707,My Baby Left Me,55,1,1,J.C. Fogerty,140460,4633440,0.99\n708,Pagan Baby,55,1,1,J.C. Fogerty,385619,12713813,0.99\n709,(Wish I Could) Hideaway,55,1,1,J.C. Fogerty,228466,7432978,0.99\n710,It's Just A Thought,55,1,1,J.C. Fogerty,237374,7778319,0.99\n711,Molina,55,1,1,J.C. Fogerty,163239,5390811,0.99\n712,Born To Move,55,1,1,J.C. Fogerty,342804,11260814,0.99\n713,Lookin' For A Reason,55,1,1,J.C. Fogerty,209789,6933135,0.99\n714,Hello Mary Lou,55,1,1,J.C. Fogerty,132832,4476563,0.99\n715,Gatas Extraordinárias,56,1,7,,212506,7095702,0.99\n716,Brasil,56,1,7,,243696,7911683,0.99\n717,Eu Sou Neguinha (Ao Vivo),56,1,7,,251768,8376000,0.99\n718,Geração Coca-Cola (Ao Vivo),56,1,7,,228153,7573301,0.99\n719,Lanterna Dos Afogados,56,1,7,,204538,6714582,0.99\n720,Coroné Antonio Bento,56,1,7,,200437,6713066,0.99\n721,\"Você Passa, Eu Acho Graça (Ao Vivo)\",56,1,7,,206733,6943576,0.99\n722,Meu Mundo Fica Completo (Com Você),56,1,7,,247771,8322240,0.99\n723,1° De Julho,56,1,7,,270262,9017535,0.99\n724,Música Urbana 2,56,1,7,,194899,6383472,0.99\n725,Vida Bandida (Ao Vivo),56,1,7,,192626,6360785,0.99\n726,Palavras Ao Vento,56,1,7,,212453,7048676,0.99\n727,Não Sei O Que Eu Quero Da Vida,56,1,7,,151849,5024963,0.99\n728,Woman Is The Nigger Of The World (Ao Vivo),56,1,7,,298919,9724145,0.99\n729,Juventude Transviada (Ao Vivo),56,1,7,,278622,9183808,0.99\n730,Malandragem,57,1,7,,247588,8165048,0.99\n731,O Segundo Sol,57,1,7,,252133,8335629,0.99\n732,Smells Like Teen Spirit (Ao Vivo),57,1,7,,316865,10384506,0.99\n733,E.C.T.,57,1,7,,227500,7571834,0.99\n734,Todo Amor Que Houver Nesta Vida,57,1,7,,227160,7420347,0.99\n735,Metrô. Linha 743,57,1,7,,174654,5837495,0.99\n736,Nós (Ao Vivo),57,1,7,,193828,6498661,0.99\n737,Na Cadência Do Samba,57,1,7,,196075,6483952,0.99\n738,Admirável Gado Novo,57,1,7,,274390,9144031,0.99\n739,Eleanor Rigby,57,1,7,,189466,6303205,0.99\n740,Socorro,57,1,7,,258586,8549393,0.99\n741,Blues Da Piedade,57,1,7,,257123,8472964,0.99\n742,Rubens,57,1,7,,211853,7026317,0.99\n743,Não Deixe O Samba Morrer - Cassia Eller e Alcione,57,1,7,,268173,8936345,0.99\n744,Mis Penas Lloraba Yo (Ao Vivo) Soy Gitano (Tangos),57,1,7,,188473,6195854,0.99\n745,Comin' Home,58,1,1,Bolin/Coverdale/Paice,235781,7644604,0.99\n746,Lady Luck,58,1,1,Cook/Coverdale,168202,5501379,0.99\n747,Gettin' Tighter,58,1,1,Bolin/Hughes,218044,7176909,0.99\n748,Dealer,58,1,1,Bolin/Coverdale,230922,7591066,0.99\n749,I Need Love,58,1,1,Bolin/Coverdale,263836,8701064,0.99\n750,Drifter,58,1,1,Bolin/Coverdale,242834,8001505,0.99\n751,Love Child,58,1,1,Bolin/Coverdale,188160,6173806,0.99\n752,This Time Around / Owed to 'G' [Instrumental],58,1,1,Bolin/Hughes/Lord,370102,11995679,0.99\n753,You Keep On Moving,58,1,1,Coverdale/Hughes,319111,10447868,0.99\n754,Speed King,59,1,1,\"Blackmore, Gillan, Glover, Lord, Paice\",264385,8587578,0.99\n755,Bloodsucker,59,1,1,\"Blackmore, Gillan, Glover, Lord, Paice\",256261,8344405,0.99\n756,Child In Time,59,1,1,\"Blackmore, Gillan, Glover, Lord, Paice\",620460,20230089,0.99\n757,Flight Of The Rat,59,1,1,\"Blackmore, Gillan, Glover, Lord, Paice\",478302,15563967,0.99\n758,Into The Fire,59,1,1,\"Blackmore, Gillan, Glover, Lord, Paice\",210259,6849310,0.99\n759,Living Wreck,59,1,1,\"Blackmore, Gillan, Glover, Lord, Paice\",274886,8993056,0.99\n760,Hard Lovin' Man,59,1,1,\"Blackmore, Gillan, Glover, Lord, Paice\",431203,13931179,0.99\n761,Fireball,60,1,1,\"Ritchie Blackmore, Ian Gillan, Roger Glover, Jon Lord, Ian Paice\",204721,6714807,0.99\n762,No No No,60,1,1,\"Ritchie Blackmore, Ian Gillan, Roger Glover, Jon Lord, Ian Paice\",414902,13646606,0.99\n763,Strange Kind Of Woman,60,1,1,\"Ritchie Blackmore, Ian Gillan, Roger Glover, Jon Lord, Ian Paice\",247092,8072036,0.99\n764,Anyone's Daughter,60,1,1,\"Ritchie Blackmore, Ian Gillan, Roger Glover, Jon Lord, Ian Paice\",284682,9354480,0.99\n765,The Mule,60,1,1,\"Ritchie Blackmore, Ian Gillan, Roger Glover, Jon Lord, Ian Paice\",322063,10638390,0.99\n766,Fools,60,1,1,\"Ritchie Blackmore, Ian Gillan, Roger Glover, Jon Lord, Ian Paice\",500427,16279366,0.99\n767,No One Came,60,1,1,\"Ritchie Blackmore, Ian Gillan, Roger Glover, Jon Lord, Ian Paice\",385880,12643813,0.99\n768,Knocking At Your Back Door,61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover\",424829,13779332,0.99\n769,Bad Attitude,61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover, Jon Lord\",307905,10035180,0.99\n770,Child In Time (Son Of Aleric - Instrumental),61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover, Jon Lord, Ian Paice\",602880,19712753,0.99\n771,Nobody's Home,61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover, Jon Lord, Ian Paice\",243017,7929493,0.99\n772,Black Night,61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover, Jon Lord, Ian Paice\",368770,12058906,0.99\n773,Perfect Strangers,61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover\",321149,10445353,0.99\n774,The Unwritten Law,61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover, Ian Paice\",295053,9740361,0.99\n775,Call Of The Wild,61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover, Jon Lord\",293851,9575295,0.99\n776,Hush,61,1,1,South,213054,6944928,0.99\n777,Smoke On The Water,61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover, Jon Lord, Ian Paice\",464378,15180849,0.99\n778,Space Trucking,61,1,1,\"Richie Blackmore, Ian Gillian, Roger Glover, Jon Lord, Ian Paice\",341185,11122183,0.99\n779,Highway Star,62,1,1,Ian Gillan/Ian Paice/Jon Lord/Ritchie Blckmore/Roger Glover,368770,12012452,0.99\n780,Maybe I'm A Leo,62,1,1,Ian Gillan/Ian Paice/Jon Lord/Ritchie Blckmore/Roger Glover,290455,9502646,0.99\n781,Pictures Of Home,62,1,1,Ian Gillan/Ian Paice/Jon Lord/Ritchie Blckmore/Roger Glover,303777,9903835,0.99\n782,Never Before,62,1,1,Ian Gillan/Ian Paice/Jon Lord/Ritchie Blckmore/Roger Glover,239830,7832790,0.99\n783,Smoke On The Water,62,1,1,Ian Gillan/Ian Paice/Jon Lord/Ritchie Blckmore/Roger Glover,340871,11246496,0.99\n784,Lazy,62,1,1,Ian Gillan/Ian Paice/Jon Lord/Ritchie Blckmore/Roger Glover,442096,14397671,0.99\n785,Space Truckin',62,1,1,Ian Gillan/Ian Paice/Jon Lord/Ritchie Blckmore/Roger Glover,272796,8981030,0.99\n786,Vavoom : Ted The Mechanic,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",257384,8510755,0.99\n787,Loosen My Strings,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",359680,11702232,0.99\n788,Soon Forgotten,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",287791,9401383,0.99\n789,Sometimes I Feel Like Screaming,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",451840,14789410,0.99\n790,Cascades : I'm Not Your Lover,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",283689,9209693,0.99\n791,The Aviator,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",320992,10532053,0.99\n792,Rosa's Cantina,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",312372,10323804,0.99\n793,A Castle Full Of Rascals,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",311693,10159566,0.99\n794,A Touch Away,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",276323,9098561,0.99\n795,Hey Cisco,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",354089,11600029,0.99\n796,Somebody Stole My Guitar,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",249443,8180421,0.99\n797,The Purpendicular Waltz,63,1,1,\"Ian Gillan, Roger Glover, Jon Lord, Steve Morse, Ian Paice\",283924,9299131,0.99\n798,King Of Dreams,64,1,1,\"Blackmore, Glover, Turner\",328385,10733847,0.99\n799,The Cut Runs Deep,64,1,1,\"Blackmore, Glover, Turner, Lord, Paice\",342752,11191650,0.99\n800,Fire In The Basement,64,1,1,\"Blackmore, Glover, Turner, Lord, Paice\",283977,9267550,0.99\n801,Truth Hurts,64,1,1,\"Blackmore, Glover, Turner\",314827,10224612,0.99\n802,Breakfast In Bed,64,1,1,\"Blackmore, Glover, Turner\",317126,10323804,0.99\n803,Love Conquers All,64,1,1,\"Blackmore, Glover, Turner\",227186,7328516,0.99\n804,Fortuneteller,64,1,1,\"Blackmore, Glover, Turner, Lord, Paice\",349335,11369671,0.99\n805,Too Much Is Not Enough,64,1,1,\"Turner, Held, Greenwood\",257724,8382800,0.99\n806,Wicked Ways,64,1,1,\"Blackmore, Glover, Turner, Lord, Paice\",393691,12826582,0.99\n807,Stormbringer,65,1,1,D.Coverdale/R.Blackmore/Ritchie Blackmore,246413,8044864,0.99\n808,Love Don't Mean a Thing,65,1,1,D.Coverdale/G.Hughes/Glenn Hughes/I.Paice/Ian Paice/J.Lord/John Lord/R.Blackmore/Ritchie Blackmore,263862,8675026,0.99\n809,Holy Man,65,1,1,D.Coverdale/G.Hughes/Glenn Hughes/J.Lord/John Lord,270236,8818093,0.99\n810,Hold On,65,1,1,D.Coverdal/G.Hughes/Glenn Hughes/I.Paice/Ian Paice/J.Lord/John Lord,306860,10022428,0.99\n811,Lady Double Dealer,65,1,1,D.Coverdale/R.Blackmore/Ritchie Blackmore,201482,6554330,0.99\n812,You Can't Do it Right (With the One You Love),65,1,1,D.Coverdale/G.Hughes/Glenn Hughes/R.Blackmore/Ritchie Blackmore,203755,6709579,0.99\n813,High Ball Shooter,65,1,1,D.Coverdale/G.Hughes/Glenn Hughes/I.Paice/Ian Paice/J.Lord/John Lord/R.Blackmore/Ritchie Blackmore,267833,8772471,0.99\n814,The Gypsy,65,1,1,D.Coverdale/G.Hughes/Glenn Hughes/I.Paice/Ian Paice/J.Lord/John Lord/R.Blackmore/Ritchie Blackmore,242886,7946614,0.99\n815,Soldier Of Fortune,65,1,1,D.Coverdale/R.Blackmore/Ritchie Blackmore,193750,6315321,0.99\n816,The Battle Rages On,66,1,1,ian paice/jon lord,356963,11626228,0.99\n817,Lick It Up,66,1,1,roger glover,240274,7792604,0.99\n818,Anya,66,1,1,jon lord/roger glover,392437,12754921,0.99\n819,Talk About Love,66,1,1,roger glover,247823,8072171,0.99\n820,Time To Kill,66,1,1,roger glover,351033,11354742,0.99\n821,Ramshackle Man,66,1,1,roger glover,334445,10874679,0.99\n822,A Twist In The Tail,66,1,1,roger glover,257462,8413103,0.99\n823,Nasty Piece Of Work,66,1,1,jon lord/roger glover,276662,9076997,0.99\n824,Solitaire,66,1,1,roger glover,282226,9157021,0.99\n825,One Man's Meat,66,1,1,roger glover,278804,9068960,0.99\n826,Pour Some Sugar On Me,67,1,1,,292519,9518842,0.99\n827,Photograph,67,1,1,,248633,8108507,0.99\n828,Love Bites,67,1,1,,346853,11305791,0.99\n829,Let's Get Rocked,67,1,1,,296019,9724150,0.99\n830,Two Steps Behind [Acoustic Version],67,1,1,,259787,8523388,0.99\n831,Animal,67,1,1,,244741,7985133,0.99\n832,Heaven Is,67,1,1,,214021,6988128,0.99\n833,Rocket,67,1,1,,247248,8092463,0.99\n834,When Love & Hate Collide,67,1,1,,257280,8364633,0.99\n835,Action,67,1,1,,220604,7130830,0.99\n836,Make Love Like A Man,67,1,1,,255660,8309725,0.99\n837,Armageddon It,67,1,1,,322455,10522352,0.99\n838,Have You Ever Needed Someone So Bad,67,1,1,,319320,10400020,0.99\n839,Rock Of Ages,67,1,1,,248424,8150318,0.99\n840,Hysteria,67,1,1,,355056,11622738,0.99\n841,Bringin' On The Heartbreak,67,1,1,,272457,8853324,0.99\n842,Roll Call,68,1,2,Jim Beard,321358,10653494,0.99\n843,Otay,68,1,2,\"John Scofield, Robert Aries, Milton Chambers and Gary Grainger\",423653,14176083,0.99\n844,Groovus Interruptus,68,1,2,Jim Beard,319373,10602166,0.99\n845,Paris On Mine,68,1,2,Jon Herington,368875,12059507,0.99\n846,In Time,68,1,2,Sylvester Stewart,368953,12287103,0.99\n847,Plan B,68,1,2,\"Dean Brown, Dennis Chambers & Jim Beard\",272039,9032315,0.99\n848,Outbreak,68,1,2,Jim Beard & Jon Herington,659226,21685807,0.99\n849,\"Baltimore, DC\",68,1,2,John Scofield,346932,11394473,0.99\n850,Talkin Loud and Saying Nothin,68,1,2,James Brown & Bobby Byrd,360411,11994859,0.99\n851,Pétala,69,1,7,,270080,8856165,0.99\n852,Meu Bem-Querer,69,1,7,,255608,8330047,0.99\n853,Cigano,69,1,7,,304692,10037362,0.99\n854,Boa Noite,69,1,7,,338755,11283582,0.99\n855,Fato Consumado,69,1,7,,211565,7018586,0.99\n856,Faltando Um Pedaço,69,1,7,,267728,8788760,0.99\n857,Álibi,69,1,7,,213237,6928434,0.99\n858,Esquinas,69,1,7,,280999,9096726,0.99\n859,Se...,69,1,7,,286432,9413777,0.99\n860,Eu Te Devoro,69,1,7,,311614,10312775,0.99\n861,Lilás,69,1,7,,274181,9049542,0.99\n862,Acelerou,69,1,7,,284081,9396942,0.99\n863,Um Amor Puro,69,1,7,,327784,10687311,0.99\n864,Samurai,70,1,7,Djavan,330997,10872787,0.99\n865,Nem Um Dia,70,1,7,Djavan,337423,11181446,0.99\n866,Oceano,70,1,7,Djavan,217338,7026441,0.99\n867,Açai,70,1,7,Djavan,270968,8893682,0.99\n868,Serrado,70,1,7,Djavan,295314,9842240,0.99\n869,Flor De Lis,70,1,7,Djavan,236355,7801108,0.99\n870,Amar É Tudo,70,1,7,Djavan,211617,7073899,0.99\n871,Azul,70,1,7,Djavan,253962,8381029,0.99\n872,Seduzir,70,1,7,Djavan,277524,9163253,0.99\n873,A Carta,70,1,7,\"Djavan - Gabriel, O Pensador\",347297,11493463,0.99\n874,Sina,70,1,7,Djavan,268173,8906539,0.99\n875,Acelerou,70,1,7,Djavan,284133,9391439,0.99\n876,Um Amor Puro,70,1,7,Djavan,327105,10664698,0.99\n877,O Bêbado e a Equilibrista,71,1,7,,223059,7306143,0.99\n878,O Mestre-Sala dos Mares,71,1,7,,186226,6180414,0.99\n879,Atrás da Porta,71,1,7,,166608,5432518,0.99\n880,\"Dois Pra Lá, Dois Pra Cá\",71,1,7,,263026,8684639,0.99\n881,Casa no Campo,71,1,7,,170788,5531841,0.99\n882,Romaria,71,1,7,,242834,7968525,0.99\n883,\"Alô, Alô, Marciano\",71,1,7,,241397,8137254,0.99\n884,Me Deixas Louca,71,1,7,,214831,6888030,0.99\n885,Fascinação,71,1,7,,180793,5793959,0.99\n886,Saudosa Maloca,71,1,7,,278125,9059416,0.99\n887,As Aparências Enganam,71,1,7,,247379,8014346,0.99\n888,Madalena,71,1,7,,157387,5243721,0.99\n889,Maria Rosa,71,1,7,,232803,7592504,0.99\n890,Aprendendo A Jogar,71,1,7,,290664,9391041,0.99\n891,Layla,72,1,6,Clapton/Gordon,430733,14115792,0.99\n892,Badge,72,1,6,Clapton/Harrison,163552,5322942,0.99\n893,I Feel Free,72,1,6,Bruce/Clapton,174576,5725684,0.99\n894,Sunshine Of Your Love,72,1,6,Bruce/Clapton,252891,8225889,0.99\n895,Crossroads,72,1,6,Clapton/Robert Johnson Arr: Eric Clapton,253335,8273540,0.99\n896,Strange Brew,72,1,6,Clapton/Collins/Pappalardi,167810,5489787,0.99\n897,White Room,72,1,6,Bruce/Clapton,301583,9872606,0.99\n898,Bell Bottom Blues,72,1,6,Clapton,304744,9946681,0.99\n899,Cocaine,72,1,6,Cale/Clapton,215928,7138399,0.99\n900,I Shot The Sheriff,72,1,6,Marley,263862,8738973,0.99\n901,After Midnight,72,1,6,Clapton/J. J. Cale,191320,6460941,0.99\n902,Swing Low Sweet Chariot,72,1,6,Clapton/Trad. Arr. Clapton,208143,6896288,0.99\n903,Lay Down Sally,72,1,6,Clapton/Levy,231732,7774207,0.99\n904,Knockin On Heavens Door,72,1,6,Clapton/Dylan,264411,8758819,0.99\n905,Wonderful Tonight,72,1,6,Clapton,221387,7326923,0.99\n906,Let It Grow,72,1,6,Clapton,297064,9742568,0.99\n907,Promises,72,1,6,Clapton/F.eldman/Linn,180401,6006154,0.99\n908,I Can't Stand It,72,1,6,Clapton,249730,8271980,0.99\n909,Signe,73,1,6,Eric Clapton,193515,6475042,0.99\n910,Before You Accuse Me,73,1,6,Eugene McDaniel,224339,7456807,0.99\n911,Hey Hey,73,1,6,Big Bill Broonzy,196466,6543487,0.99\n912,Tears In Heaven,73,1,6,\"Eric Clapton, Will Jennings\",274729,9032835,0.99\n913,Lonely Stranger,73,1,6,Eric Clapton,328724,10894406,0.99\n914,Nobody Knows You When You're Down & Out,73,1,6,Jimmy Cox,231836,7669922,0.99\n915,Layla,73,1,6,\"Eric Clapton, Jim Gordon\",285387,9490542,0.99\n916,Running On Faith,73,1,6,Jerry Lynn Williams,378984,12536275,0.99\n917,Walkin' Blues,73,1,6,Robert Johnson,226429,7435192,0.99\n918,Alberta,73,1,6,Traditional,222406,7412975,0.99\n919,San Francisco Bay Blues,73,1,6,Jesse Fuller,203363,6724021,0.99\n920,Malted Milk,73,1,6,Robert Johnson,216528,7096781,0.99\n921,Old Love,73,1,6,\"Eric Clapton, Robert Cray\",472920,15780747,0.99\n922,Rollin' And Tumblin',73,1,6,McKinley Morgenfield (Muddy Waters),251768,8407355,0.99\n923,Collision,74,1,4,Jon Hudson/Mike Patton,204303,6656596,0.99\n924,Stripsearch,74,1,4,Jon Hudson/Mike Bordin/Mike Patton,270106,8861119,0.99\n925,Last Cup Of Sorrow,74,1,4,Bill Gould/Mike Patton,251663,8221247,0.99\n926,Naked In Front Of The Computer,74,1,4,Mike Patton,128757,4225077,0.99\n927,Helpless,74,1,4,Bill Gould/Mike Bordin/Mike Patton,326217,10753135,0.99\n928,Mouth To Mouth,74,1,4,Bill Gould/Jon Hudson/Mike Bordin/Mike Patton,228493,7505887,0.99\n929,Ashes To Ashes,74,1,4,Bill Gould/Jon Hudson/Mike Bordin/Mike Patton/Roddy Bottum,217391,7093746,0.99\n930,She Loves Me Not,74,1,4,Bill Gould/Mike Bordin/Mike Patton,209867,6887544,0.99\n931,Got That Feeling,74,1,4,Mike Patton,140852,4643227,0.99\n932,Paths Of Glory,74,1,4,Bill Gould/Jon Hudson/Mike Bordin/Mike Patton/Roddy Bottum,257253,8436300,0.99\n933,Home Sick Home,74,1,4,Mike Patton,119040,3898976,0.99\n934,Pristina,74,1,4,Bill Gould/Mike Patton,232698,7497361,0.99\n935,Land Of Sunshine,75,1,4,,223921,7353567,0.99\n936,Caffeine,75,1,4,,267937,8747367,0.99\n937,Midlife Crisis,75,1,4,,263235,8628841,0.99\n938,RV,75,1,4,,223242,7288162,0.99\n939,Smaller And Smaller,75,1,4,,310831,10180103,0.99\n940,Everything's Ruined,75,1,4,,273658,9010917,0.99\n941,Malpractice,75,1,4,,241371,7900683,0.99\n942,Kindergarten,75,1,4,,270680,8853647,0.99\n943,Be Aggressive,75,1,4,,222432,7298027,0.99\n944,A Small Victory,75,1,4,,297168,9733572,0.99\n945,Crack Hitler,75,1,4,,279144,9162435,0.99\n946,Jizzlobber,75,1,4,,398341,12926140,0.99\n947,Midnight Cowboy,75,1,4,,251924,8242626,0.99\n948,Easy,75,1,4,,185835,6073008,0.99\n949,Get Out,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",137482,4524972,0.99\n950,Ricochet,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",269400,8808812,0.99\n951,Evidence,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton, Trey Spruance\",293590,9626136,0.99\n952,The Gentle Art Of Making Enemies,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",209319,6908609,0.99\n953,Star A.D.,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",203807,6747658,0.99\n954,Cuckoo For Caca,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton, Trey Spruance\",222902,7388369,0.99\n955,Caralho Voador,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton, Trey Spruance\",242102,8029054,0.99\n956,Ugly In The Morning,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",186435,6224997,0.99\n957,Digging The Grave,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",185129,6109259,0.99\n958,Take This Bottle,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton, Trey Spruance\",298997,9779971,0.99\n959,King For A Day,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton, Trey Spruance\",395859,13163733,0.99\n960,What A Day,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",158275,5203430,0.99\n961,The Last To Know,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",267833,8736776,0.99\n962,Just A Man,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",336666,11031254,0.99\n963,Absolute Zero,76,1,1,\"Mike Bordin, Billy Gould, Mike Patton\",181995,5929427,0.99\n964,From Out Of Nowhere,77,1,4,Faith No More,202527,6587802,0.99\n965,Epic,77,1,4,Faith No More,294008,9631296,0.99\n966,Falling To Pieces,77,1,4,Faith No More,316055,10333123,0.99\n967,Surprise! You're Dead!,77,1,4,Faith No More,147226,4823036,0.99\n968,Zombie Eaters,77,1,4,Faith No More,360881,11835367,0.99\n969,The Real Thing,77,1,4,Faith No More,493635,16233080,0.99\n970,Underwater Love,77,1,4,Faith No More,231993,7634387,0.99\n971,The Morning After,77,1,4,Faith No More,223764,7355898,0.99\n972,Woodpecker From Mars,77,1,4,Faith No More,340532,11174250,0.99\n973,War Pigs,77,1,4,\"Tony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne\",464770,15267802,0.99\n974,Edge Of The World,77,1,4,Faith No More,250357,8235607,0.99\n975,Deixa Entrar,78,1,7,,33619,1095012,0.99\n976,Falamansa Song,78,1,7,,237165,7921313,0.99\n977,Xote Dos Milagres,78,1,7,,269557,8897778,0.99\n978,Rindo À Toa,78,1,7,,222066,7365321,0.99\n979,Confidência,78,1,7,,222197,7460829,0.99\n980,Forró De Tóquio,78,1,7,,169273,5588756,0.99\n981,Zeca Violeiro,78,1,7,,143673,4781949,0.99\n982,Avisa,78,1,7,,355030,11844320,0.99\n983,Principiando/Decolagem,78,1,7,,116767,3923789,0.99\n984,Asas,78,1,7,,231915,7711669,0.99\n985,Medo De Escuro,78,1,7,,213760,7056323,0.99\n986,Oração,78,1,7,,271072,9003882,0.99\n987,Minha Gata,78,1,7,,181838,6039502,0.99\n988,Desaforo,78,1,7,,174524,5853561,0.99\n989,In Your Honor,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",230191,7468463,0.99\n990,No Way Back,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",196675,6421400,0.99\n991,Best Of You,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",255712,8363467,0.99\n992,DOA,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",252186,8232342,0.99\n993,Hell,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",117080,3819255,0.99\n994,The Last Song,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",199523,6496742,0.99\n995,Free Me,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",278700,9109340,0.99\n996,Resolve,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",288731,9416186,0.99\n997,The Deepest Blues Are Black,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",238419,7735473,0.99\n998,End Over End,79,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett\",352078,11395296,0.99\n999,Still,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",313182,10323157,0.99\n1000,What If I Do?,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",302994,9929799,0.99\n1001,Miracle,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",209684,6877994,0.99\n1002,Another Round,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",265848,8752670,0.99\n1003,Friend Of A Friend,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",193280,6355088,0.99\n1004,Over And Out,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",316264,10428382,0.99\n1005,On The Mend,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",271908,9071997,0.99\n1006,Virginia Moon,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",229198,7494639,0.99\n1007,Cold Day In The Sun,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",200724,6596617,0.99\n1008,Razor,80,1,1,\"Dave Grohl, Taylor Hawkins, Nate Mendel, Chris Shiflett/FOO FIGHTERS\",293276,9721373,0.99\n1009,All My Life,81,1,4,Foo Fighters,263653,8665545,0.99\n1010,Low,81,1,4,Foo Fighters,268120,8847196,0.99\n1011,Have It All,81,1,4,Foo Fighters,298057,9729292,0.99\n1012,Times Like These,81,1,4,Foo Fighters,266370,8624691,0.99\n1013,Disenchanted Lullaby,81,1,4,Foo Fighters,273528,8919111,0.99\n1014,Tired Of You,81,1,4,Foo Fighters,311353,10094743,0.99\n1015,Halo,81,1,4,Foo Fighters,306442,10026371,0.99\n1016,Lonely As You,81,1,4,Foo Fighters,277185,9022628,0.99\n1017,Overdrive,81,1,4,Foo Fighters,270550,8793187,0.99\n1018,Burn Away,81,1,4,Foo Fighters,298396,9678073,0.99\n1019,Come Back,81,1,4,Foo Fighters,469968,15371980,0.99\n1020,Doll,82,1,1,\"Dave, Taylor, Nate, Chris\",83487,2702572,0.99\n1021,Monkey Wrench,82,1,1,\"Dave, Taylor, Nate, Chris\",231523,7527531,0.99\n1022,\"Hey, Johnny Park!\",82,1,1,\"Dave, Taylor, Nate, Chris\",248528,8079480,0.99\n1023,My Poor Brain,82,1,1,\"Dave, Taylor, Nate, Chris\",213446,6973746,0.99\n1024,Wind Up,82,1,1,\"Dave, Taylor, Nate, Chris\",152163,4950667,0.99\n1025,Up In Arms,82,1,1,\"Dave, Taylor, Nate, Chris\",135732,4406227,0.99\n1026,My Hero,82,1,1,\"Dave, Taylor, Nate, Chris\",260101,8472365,0.99\n1027,See You,82,1,1,\"Dave, Taylor, Nate, Chris\",146782,4888173,0.99\n1028,Enough Space,82,1,1,Dave Grohl,157387,5169280,0.99\n1029,February Stars,82,1,1,\"Dave, Taylor, Nate, Chris\",289306,9344875,0.99\n1030,Everlong,82,1,1,Dave Grohl,250749,8270816,0.99\n1031,Walking After You,82,1,1,Dave Grohl,303856,9898992,0.99\n1032,New Way Home,82,1,1,\"Dave, Taylor, Nate, Chris\",342230,11205664,0.99\n1033,My Way,83,1,12,claude françois/gilles thibault/jacques revaux/paul anka,275879,8928684,0.99\n1034,Strangers In The Night,83,1,12,berthold kaempfert/charles singleton/eddie snyder,155794,5055295,0.99\n1035,\"New York, New York\",83,1,12,fred ebb/john kander,206001,6707993,0.99\n1036,I Get A Kick Out Of You,83,1,12,cole porter,194429,6332441,0.99\n1037,Something Stupid,83,1,12,carson c. parks,158615,5210643,0.99\n1038,Moon River,83,1,12,henry mancini/johnny mercer,198922,6395808,0.99\n1039,What Now My Love,83,1,12,carl sigman/gilbert becaud/pierre leroyer,149995,4913383,0.99\n1040,Summer Love,83,1,12,hans bradtke/heinz meier/johnny mercer,174994,5693242,0.99\n1041,For Once In My Life,83,1,12,orlando murden/ronald miller,171154,5557537,0.99\n1042,Love And Marriage,83,1,12,jimmy van heusen/sammy cahn,89730,2930596,0.99\n1043,They Can't Take That Away From Me,83,1,12,george gershwin/ira gershwin,161227,5240043,0.99\n1044,My Kind Of Town,83,1,12,jimmy van heusen/sammy cahn,188499,6119915,0.99\n1045,Fly Me To The Moon,83,1,12,bart howard,149263,4856954,0.99\n1046,I've Got You Under My Skin,83,1,12,cole porter,210808,6883787,0.99\n1047,The Best Is Yet To Come,83,1,12,carolyn leigh/cy coleman,173583,5633730,0.99\n1048,It Was A Very Good Year,83,1,12,ervin drake,266605,8554066,0.99\n1049,Come Fly With Me,83,1,12,jimmy van heusen/sammy cahn,190458,6231029,0.99\n1050,That's Life,83,1,12,dean kay thompson/kelly gordon,187010,6095727,0.99\n1051,The Girl From Ipanema,83,1,12,antonio carlos jobim/norman gimbel/vinicius de moraes,193750,6410674,0.99\n1052,The Lady Is A Tramp,83,1,12,lorenz hart/richard rodgers,184111,5987372,0.99\n1053,\"Bad, Bad Leroy Brown\",83,1,12,jim croce,169900,5548581,0.99\n1054,Mack The Knife,83,1,12,bert brecht/kurt weill/marc blitzstein,292075,9541052,0.99\n1055,Loves Been Good To Me,83,1,12,rod mckuen,203964,6645365,0.99\n1056,L.A. Is My Lady,83,1,12,alan bergman/marilyn bergman/peggy lipton jones/quincy jones,193175,6378511,0.99\n1057,Entrando Na Sua (Intro),84,1,7,,179252,5840027,0.99\n1058,Nervosa,84,1,7,,229537,7680421,0.99\n1059,Funk De Bamba (Com Fernanda Abreu),84,1,7,,237191,7866165,0.99\n1060,Call Me At Cleo´s,84,1,7,,236617,7920510,0.99\n1061,Olhos Coloridos (Com Sandra De Sá),84,1,7,,321332,10567404,0.99\n1062,Zambação,84,1,7,,301113,10030604,0.99\n1063,Funk Hum,84,1,7,,244453,8084475,0.99\n1064,Forty Days (Com DJ Hum),84,1,7,,221727,7347172,0.99\n1065,Balada Da Paula,84,1,7,Emerson Villani,322821,10603717,0.99\n1066,Dujji,84,1,7,,324597,10833935,0.99\n1067,Meu Guarda-Chuva,84,1,7,,248528,8216625,0.99\n1068,Motéis,84,1,7,,213498,7041077,0.99\n1069,Whistle Stop,84,1,7,,526132,17533664,0.99\n1070,16 Toneladas,84,1,7,,191634,6390885,0.99\n1071,Divirta-Se (Saindo Da Sua),84,1,7,,74919,2439206,0.99\n1072,Forty Days Instrumental,84,1,7,,292493,9584317,0.99\n1073,Óia Eu Aqui De Novo,85,1,10,,219454,7469735,0.99\n1074,Baião Da Penha,85,1,10,,247928,8393047,0.99\n1075,Esperando Na Janela,85,1,10,Manuca/Raimundinho DoAcordion/Targino Godim,261041,8660617,0.99\n1076,Juazeiro,85,1,10,Humberto Teixeira/Luiz Gonzaga,222275,7349779,0.99\n1077,Último Pau-De-Arara,85,1,10,Corumbá/José Gumarães/Venancio,200437,6638563,0.99\n1078,Asa Branca,85,1,10,Humberto Teixeira/Luiz Gonzaga,217051,7387183,0.99\n1079,Qui Nem Jiló,85,1,10,Humberto Teixeira/Luiz Gonzaga,204695,6937472,0.99\n1080,Assum Preto,85,1,10,Humberto Teixeira/Luiz Gonzaga,199653,6625000,0.99\n1081,Pau-De-Arara,85,1,10,\"Guio De Morais E Seus \"\"Parentes\"\"/Luiz Gonzaga\",191660,6340649,0.99\n1082,A Volta Da Asa Branca,85,1,10,Luiz Gonzaga/Zé Dantas,271020,9098093,0.99\n1083,O Amor Daqui De Casa,85,1,10,Gilberto Gil,148636,4888292,0.99\n1084,As Pegadas Do Amor,85,1,10,Gilberto Gil,209136,6899062,0.99\n1085,Lamento Sertanejo,85,1,10,Dominguinhos/Gilberto Gil,260963,8518290,0.99\n1086,Casinha Feliz,85,1,10,Gilberto Gil,32287,1039615,0.99\n1087,Introdução (Live),86,1,7,,154096,5227579,0.99\n1088,Palco (Live),86,1,7,,238315,8026622,0.99\n1089,Is This Love (Live),86,1,7,,295262,9819759,0.99\n1090,Stir It Up (Live),86,1,7,,282409,9594738,0.99\n1091,Refavela (Live),86,1,7,,236695,7985305,0.99\n1092,Vendedor De Caranguejo (Live),86,1,7,,248842,8358128,0.99\n1093,Quanta (Live),86,1,7,,357485,11774865,0.99\n1094,Estrela (Live),86,1,7,,285309,9436411,0.99\n1095,Pela Internet (Live),86,1,7,,263471,8804401,0.99\n1096,Cérebro Eletrônico (Live),86,1,7,,231627,7805352,0.99\n1097,Opachorô (Live),86,1,7,,259526,8596384,0.99\n1098,Copacabana (Live),86,1,7,,289671,9673672,0.99\n1099,A Novidade (Live),86,1,7,,316969,10508000,0.99\n1100,Ghandi (Live),86,1,7,,222458,7481950,0.99\n1101,De Ouro E Marfim (Live),86,1,7,,234971,7838453,0.99\n1102,Doce De Carnaval (Candy All),87,1,2,,356101,11998470,0.99\n1103,Lamento De Carnaval,87,1,2,,294530,9819276,0.99\n1104,Pretinha,87,1,2,,265273,8914579,0.99\n1105,A Novidade,73,1,7,Gilberto Gil,324780,10765600,0.99\n1106,Tenho Sede,73,1,7,Gilberto Gil,261616,8708114,0.99\n1107,Refazenda,73,1,7,Gilberto Gil,218305,7237784,0.99\n1108,Realce,73,1,7,Gilberto Gil,264489,8847612,0.99\n1109,Esotérico,73,1,7,Gilberto Gil,317779,10530533,0.99\n1110,Drão,73,1,7,Gilberto Gil,301453,9931950,0.99\n1111,A Paz,73,1,7,Gilberto Gil,293093,9593064,0.99\n1112,Beira Mar,73,1,7,Gilberto Gil,295444,9597994,0.99\n1113,Sampa,73,1,7,Gilberto Gil,225697,7469905,0.99\n1114,Parabolicamará,73,1,7,Gilberto Gil,284943,9543435,0.99\n1115,Tempo Rei,73,1,7,Gilberto Gil,302733,10019269,0.99\n1116,Expresso 2222,73,1,7,Gilberto Gil,284760,9690577,0.99\n1117,Aquele Abraço,73,1,7,Gilberto Gil,263993,8805003,0.99\n1118,Palco,73,1,7,Gilberto Gil,270550,9049901,0.99\n1119,Toda Menina Baiana,73,1,7,Gilberto Gil,278177,9351000,0.99\n1120,Sítio Do Pica-Pau Amarelo,73,1,7,Gilberto Gil,218070,7217955,0.99\n1121,Straight Out Of Line,88,1,3,Sully Erna,259213,8511877,0.99\n1122,Faceless,88,1,3,Sully Erna,216006,6992417,0.99\n1123,Changes,88,1,3,Sully Erna; Tony Rombola,260022,8455835,0.99\n1124,Make Me Believe,88,1,3,Sully Erna,248607,8075050,0.99\n1125,I Stand Alone,88,1,3,Sully Erna,246125,8017041,0.99\n1126,Re-Align,88,1,3,Sully Erna,260884,8513891,0.99\n1127,I Fucking Hate You,88,1,3,Sully Erna,247170,8059642,0.99\n1128,Releasing The Demons,88,1,3,Sully Erna,252760,8276372,0.99\n1129,Dead And Broken,88,1,3,Sully Erna,251454,8206611,0.99\n1130,I Am,88,1,3,Sully Erna,239516,7803270,0.99\n1131,The Awakening,88,1,3,Sully Erna,89547,3035251,0.99\n1132,Serenity,88,1,3,Sully Erna; Tony Rombola,274834,9172976,0.99\n1133,American Idiot,89,1,4,\"Billie Joe Armstrong, Mike Dirnt, Tré Cool\",174419,5705793,0.99\n1134,Jesus Of Suburbia / City Of The Damned / I Don't Care / Dearly Beloved / Tales Of Another Broken Home,89,1,4,Billie Joe Armstrong/Green Day,548336,17875209,0.99\n1135,Holiday,89,1,4,\"Billie Joe Armstrong, Mike Dirnt, Tré Cool\",232724,7599602,0.99\n1136,Boulevard Of Broken Dreams,89,1,4,\"Mike Dint, Billie Joe, Tré Cool\",260858,8485122,0.99\n1137,Are We The Waiting,89,1,4,Green Day,163004,5328329,0.99\n1138,St. Jimmy,89,1,4,Green Day,175307,5716589,0.99\n1139,Give Me Novacaine,89,1,4,Green Day,205871,6752485,0.99\n1140,She's A Rebel,89,1,4,Green Day,120528,3901226,0.99\n1141,Extraordinary Girl,89,1,4,Green Day,214021,6975177,0.99\n1142,Letterbomb,89,1,4,Green Day,246151,7980902,0.99\n1143,Wake Me Up When September Ends,89,1,4,\"Mike Dint, Billie Joe, Tré Cool\",285753,9325597,0.99\n1144,Homecoming / The Death Of St. Jimmy / East 12th St. / Nobody Likes You / Rock And Roll Girlfriend / We're Coming Home Again,89,1,4,Mike Dirnt/Tré Cool,558602,18139840,0.99\n1145,Whatsername,89,1,4,Green Day,252316,8244843,0.99\n1146,Welcome to the Jungle,90,2,1,,273552,4538451,0.99\n1147,It's So Easy,90,2,1,,202824,3394019,0.99\n1148,Nightrain,90,2,1,,268537,4457283,0.99\n1149,Out Ta Get Me,90,2,1,,263893,4382147,0.99\n1150,Mr. Brownstone,90,2,1,,228924,3816323,0.99\n1151,Paradise City,90,2,1,,406347,6687123,0.99\n1152,My Michelle,90,2,1,,219961,3671299,0.99\n1153,Think About You,90,2,1,,231640,3860275,0.99\n1154,Sweet Child O' Mine,90,2,1,,356424,5879347,0.99\n1155,You're Crazy,90,2,1,,197135,3301971,0.99\n1156,Anything Goes,90,2,1,,206400,3451891,0.99\n1157,Rocket Queen,90,2,1,,375349,6185539,0.99\n1158,Right Next Door to Hell,91,2,1,,182321,3175950,0.99\n1159,Dust N' Bones,91,2,1,,298374,5053742,0.99\n1160,Live and Let Die,91,2,1,,184016,3203390,0.99\n1161,Don't Cry (Original),91,2,1,,284744,4833259,0.99\n1162,Perfect Crime,91,2,1,,143637,2550030,0.99\n1163,You Ain't the First,91,2,1,,156268,2754414,0.99\n1164,Bad Obsession,91,2,1,,328282,5537678,0.99\n1165,Back off Bitch,91,2,1,,303436,5135662,0.99\n1166,Double Talkin' Jive,91,2,1,,203637,3520862,0.99\n1167,November Rain,91,2,1,,537540,8923566,0.99\n1168,The Garden,91,2,1,,322175,5438862,0.99\n1169,Garden of Eden,91,2,1,,161539,2839694,0.99\n1170,Don't Damn Me,91,2,1,,318901,5385886,0.99\n1171,Bad Apples,91,2,1,,268351,4567966,0.99\n1172,Dead Horse,91,2,1,,257600,4394014,0.99\n1173,Coma,91,2,1,,616511,10201342,0.99\n1174,Civil War,92,1,3,Duff McKagan/Slash/W. Axl Rose,461165,15046579,0.99\n1175,14 Years,92,1,3,Izzy Stradlin'/W. Axl Rose,261355,8543664,0.99\n1176,Yesterdays,92,1,3,Billy/Del James/W. Axl Rose/West Arkeen,196205,6398489,0.99\n1177,Knockin' On Heaven's Door,92,1,3,Bob Dylan,336457,10986716,0.99\n1178,Get In The Ring,92,1,3,Duff McKagan/Slash/W. Axl Rose,341054,11134105,0.99\n1179,Shotgun Blues,92,1,3,W. Axl Rose,203206,6623916,0.99\n1180,Breakdown,92,1,3,W. Axl Rose,424960,13978284,0.99\n1181,Pretty Tied Up,92,1,3,Izzy Stradlin',287477,9408754,0.99\n1182,Locomotive,92,1,3,Slash/W. Axl Rose,522396,17236842,0.99\n1183,So Fine,92,1,3,Duff McKagan,246491,8039484,0.99\n1184,Estranged,92,1,3,W. Axl Rose,563800,18343787,0.99\n1185,You Could Be Mine,92,1,3,Izzy Stradlin'/W. Axl Rose,343875,11207355,0.99\n1186,Don't Cry,92,1,3,Izzy Stradlin'/W. Axl Rose,284238,9222458,0.99\n1187,My World,92,1,3,W. Axl Rose,84532,2764045,0.99\n1188,Colibri,93,1,2,Richard Bull,361012,12055329,0.99\n1189,Love Is The Colour,93,1,2,R. Carless,251585,8419165,0.99\n1190,Magnetic Ocean,93,1,2,Patrick Claher/Richard Bull,321123,10720741,0.99\n1191,Deep Waters,93,1,2,Richard Bull,396460,13075359,0.99\n1192,L'Arc En Ciel De Miles,93,1,2,Kevin Robinson/Richard Bull,242390,8053997,0.99\n1193,Gypsy,93,1,2,Kevin Robinson,330997,11083374,0.99\n1194,Journey Into Sunlight,93,1,2,Jean Paul Maunick,249756,8241177,0.99\n1195,Sunchild,93,1,2,Graham Harvey,259970,8593143,0.99\n1196,Millenium,93,1,2,Maxton Gig Beesley Jnr.,379167,12511939,0.99\n1197,Thinking 'Bout Tomorrow,93,1,2,Fayyaz Virgi/Richard Bull,355395,11865384,0.99\n1198,Jacob's Ladder,93,1,2,Julian Crampton,367647,12201595,0.99\n1199,She Wears Black,93,1,2,G Harvey/R Hope-Taylor,528666,17617944,0.99\n1200,Dark Side Of The Cog,93,1,2,Jean Paul Maunick,377155,12491122,0.99\n1201,Different World,94,2,1,,258692,4383764,0.99\n1202,These Colours Don't Run,94,2,1,,412152,6883500,0.99\n1203,Brighter Than a Thousand Suns,94,2,1,,526255,8721490,0.99\n1204,The Pilgrim,94,2,1,,307593,5172144,0.99\n1205,The Longest Day,94,2,1,,467810,7785748,0.99\n1206,Out of the Shadows,94,2,1,,336896,5647303,0.99\n1207,The Reincarnation of Benjamin Breeg,94,2,1,,442106,7367736,0.99\n1208,For the Greater Good of God,94,2,1,,564893,9367328,0.99\n1209,Lord of Light,94,2,1,,444614,7393698,0.99\n1210,The Legacy,94,2,1,,562966,9314287,0.99\n1211,Hallowed Be Thy Name (Live) [Non Album Bonus Track],94,2,1,,431262,7205816,0.99\n1212,The Number Of The Beast,95,1,3,Steve Harris,294635,4718897,0.99\n1213,The Trooper,95,1,3,Steve Harris,235311,3766272,0.99\n1214,Prowler,95,1,3,Steve Harris,255634,4091904,0.99\n1215,Transylvania,95,1,3,Steve Harris,265874,4255744,0.99\n1216,Remember Tomorrow,95,1,3,Paul Di'Anno/Steve Harris,352731,5648438,0.99\n1217,Where Eagles Dare,95,1,3,Steve Harris,289358,4630528,0.99\n1218,Sanctuary,95,1,3,David Murray/Paul Di'Anno/Steve Harris,293250,4694016,0.99\n1219,Running Free,95,1,3,Paul Di'Anno/Steve Harris,228937,3663872,0.99\n1220,Run To The Hilss,95,1,3,Steve Harris,237557,3803136,0.99\n1221,2 Minutes To Midnight,95,1,3,Adrian Smith/Bruce Dickinson,337423,5400576,0.99\n1222,Iron Maiden,95,1,3,Steve Harris,324623,5195776,0.99\n1223,Hallowed Be Thy Name,95,1,3,Steve Harris,471849,7550976,0.99\n1224,Be Quick Or Be Dead,96,1,3,Bruce Dickinson/Janick Gers,196911,3151872,0.99\n1225,From Here To Eternity,96,1,3,Steve Harris,259866,4159488,0.99\n1226,Can I Play With Madness,96,1,3,Adrian Smith/Bruce Dickinson/Steve Harris,282488,4521984,0.99\n1227,Wasting Love,96,1,3,Bruce Dickinson/Janick Gers,347846,5566464,0.99\n1228,Tailgunner,96,1,3,Bruce Dickinson/Steve Harris,249469,3993600,0.99\n1229,The Evil That Men Do,96,1,3,Adrian Smith/Bruce Dickinson/Steve Harris,325929,5216256,0.99\n1230,Afraid To Shoot Strangers,96,1,3,Steve Harris,407980,6529024,0.99\n1231,Bring Your Daughter... To The Slaughter,96,1,3,Bruce Dickinson,317727,5085184,0.99\n1232,Heaven Can Wait,96,1,3,Steve Harris,448574,7178240,0.99\n1233,The Clairvoyant,96,1,3,Steve Harris,269871,4319232,0.99\n1234,Fear Of The Dark,96,1,3,Steve Harris,431333,6906078,0.99\n1235,The Wicker Man,97,1,1,Adrian Smith/Bruce Dickinson/Steve Harris,275539,11022464,0.99\n1236,Ghost Of The Navigator,97,1,1,Bruce Dickinson/Janick Gers/Steve Harris,410070,16404608,0.99\n1237,Brave New World,97,1,1,Bruce Dickinson/David Murray/Steve Harris,378984,15161472,0.99\n1238,Blood Brothers,97,1,1,Steve Harris,434442,17379456,0.99\n1239,The Mercenary,97,1,1,Janick Gers/Steve Harris,282488,11300992,0.99\n1240,Dream Of Mirrors,97,1,1,Janick Gers/Steve Harris,561162,22448256,0.99\n1241,The Fallen Angel,97,1,1,Adrian Smith/Steve Harris,240718,9629824,0.99\n1242,The Nomad,97,1,1,David Murray/Steve Harris,546115,21846144,0.99\n1243,Out Of The Silent Planet,97,1,1,Bruce Dickinson/Janick Gers/Steve Harris,385541,15423616,0.99\n1244,The Thin Line Between Love & Hate,97,1,1,David Murray/Steve Harris,506801,20273280,0.99\n1245,Wildest Dreams,98,1,13,Adrian Smith/Steve Harris,232777,9312384,0.99\n1246,Rainmaker,98,1,13,Bruce Dickinson/David Murray/Steve Harris,228623,9146496,0.99\n1247,No More Lies,98,1,13,Steve Harris,441782,17672320,0.99\n1248,Montsegur,98,1,13,Bruce Dickinson/Janick Gers/Steve Harris,350484,14020736,0.99\n1249,Dance Of Death,98,1,13,Janick Gers/Steve Harris,516649,20670727,0.99\n1250,Gates Of Tomorrow,98,1,13,Bruce Dickinson/Janick Gers/Steve Harris,312032,12482688,0.99\n1251,New Frontier,98,1,13,Adrian Smith/Bruce Dickinson/Nicko McBrain,304509,12181632,0.99\n1252,Paschendale,98,1,13,Adrian Smith/Steve Harris,508107,20326528,0.99\n1253,Face In The Sand,98,1,13,Adrian Smith/Bruce Dickinson/Steve Harris,391105,15648948,0.99\n1254,Age Of Innocence,98,1,13,David Murray/Steve Harris,370468,14823478,0.99\n1255,Journeyman,98,1,13,Bruce Dickinson/David Murray/Steve Harris,427023,17082496,0.99\n1256,Be Quick Or Be Dead,99,1,1,Bruce Dickinson/Janick Gers,204512,8181888,0.99\n1257,From Here To Eternity,99,1,1,Steve Harris,218357,8739038,0.99\n1258,Afraid To Shoot Strangers,99,1,1,Steve Harris,416496,16664589,0.99\n1259,Fear Is The Key,99,1,1,Bruce Dickinson/Janick Gers,335307,13414528,0.99\n1260,Childhood's End,99,1,1,Steve Harris,280607,11225216,0.99\n1261,Wasting Love,99,1,1,Bruce Dickinson/Janick Gers,350981,14041216,0.99\n1262,The Fugitive,99,1,1,Steve Harris,294112,11765888,0.99\n1263,Chains Of Misery,99,1,1,Bruce Dickinson/David Murray,217443,8700032,0.99\n1264,The Apparition,99,1,1,Janick Gers/Steve Harris,234605,9386112,0.99\n1265,Judas Be My Guide,99,1,1,Bruce Dickinson/David Murray,188786,7553152,0.99\n1266,Weekend Warrior,99,1,1,Janick Gers/Steve Harris,339748,13594678,0.99\n1267,Fear Of The Dark,99,1,1,Steve Harris,436976,17483789,0.99\n1268,01 - Prowler,100,1,6,Steve Harris,236173,5668992,0.99\n1269,02 - Sanctuary,100,1,6,David Murray/Paul Di'Anno/Steve Harris,196284,4712576,0.99\n1270,03 - Remember Tomorrow,100,1,6,Harris/Paul Di´Anno,328620,7889024,0.99\n1271,04 - Running Free,100,1,6,Harris/Paul Di´Anno,197276,4739122,0.99\n1272,05 - Phantom of the Opera,100,1,6,Steve Harris,428016,10276872,0.99\n1273,06 - Transylvania,100,1,6,Steve Harris,259343,6226048,0.99\n1274,07 - Strange World,100,1,6,Steve Harris,332460,7981184,0.99\n1275,08 - Charlotte the Harlot,100,1,6,Murray  Dave,252708,6066304,0.99\n1276,09 - Iron Maiden,100,1,6,Steve Harris,216058,5189891,0.99\n1277,The Ides Of March,101,1,13,Steve Harris,105926,2543744,0.99\n1278,Wrathchild,101,1,13,Steve Harris,174471,4188288,0.99\n1279,Murders In The Rue Morgue,101,1,13,Steve Harris,258377,6205786,0.99\n1280,Another Life,101,1,13,Steve Harris,203049,4874368,0.99\n1281,Genghis Khan,101,1,13,Steve Harris,187141,4493440,0.99\n1282,Innocent Exile,101,1,13,Di´Anno/Harris,232515,5584861,0.99\n1283,Killers,101,1,13,Steve Harris,300956,7227440,0.99\n1284,Prodigal Son,101,1,13,Steve Harris,372349,8937600,0.99\n1285,Purgatory,101,1,13,Steve Harris,200150,4804736,0.99\n1286,Drifter,101,1,13,Steve Harris,288757,6934660,0.99\n1287,Intro- Churchill S Speech,102,1,13,,48013,1154488,0.99\n1288,Aces High,102,1,13,,276375,6635187,0.99\n1289,2 Minutes To Midnight,102,1,3,Smith/Dickinson,366550,8799380,0.99\n1290,The Trooper,102,1,3,Harris,268878,6455255,0.99\n1291,Revelations,102,1,3,Dickinson,371826,8926021,0.99\n1292,Flight Of Icarus,102,1,3,Smith/Dickinson,229982,5521744,0.99\n1293,Rime Of The Ancient Mariner,102,1,3,,789472,18949518,0.99\n1294,Powerslave,102,1,3,,454974,10921567,0.99\n1295,The Number Of The Beast,102,1,3,Harris,275121,6605094,0.99\n1296,Hallowed Be Thy Name,102,1,3,Harris,451422,10836304,0.99\n1297,Iron Maiden,102,1,3,Harris,261955,6289117,0.99\n1298,Run To The Hills,102,1,3,Harris,231627,5561241,0.99\n1299,Running Free,102,1,3,Harris/Di Anno,204617,4912986,0.99\n1300,Wrathchild,102,1,13,Steve Harris,183666,4410181,0.99\n1301,Acacia Avenue,102,1,13,,379872,9119118,0.99\n1302,Children Of The Damned,102,1,13,Steve Harris,278177,6678446,0.99\n1303,Die With Your Boots On,102,1,13,Adrian Smith/Bruce Dickinson/Steve Harris,314174,7542367,0.99\n1304,Phantom Of The Opera,102,1,13,Steve Harris,441155,10589917,0.99\n1305,Be Quick Or Be Dead,103,1,1,,233142,5599853,0.99\n1306,The Number Of The Beast,103,1,1,,294008,7060625,0.99\n1307,Wrathchild,103,1,1,,174106,4182963,0.99\n1308,From Here To Eternity,103,1,1,,284447,6831163,0.99\n1309,Can I Play With Madness,103,1,1,,213106,5118995,0.99\n1310,Wasting Love,103,1,1,,336953,8091301,0.99\n1311,Tailgunner,103,1,1,,247640,5947795,0.99\n1312,The Evil That Men Do,103,1,1,,478145,11479913,0.99\n1313,Afraid To Shoot Strangers,103,1,1,,412525,9905048,0.99\n1314,Fear Of The Dark,103,1,1,,431542,10361452,0.99\n1315,Bring Your Daughter... To The Slaughter...,104,1,1,,376711,9045532,0.99\n1316,The Clairvoyant,104,1,1,,262426,6302648,0.99\n1317,Heaven Can Wait,104,1,1,,440555,10577743,0.99\n1318,Run To The Hills,104,1,1,,235859,5665052,0.99\n1319,2 Minutes To Midnight,104,1,1,Adrian Smith/Bruce Dickinson,338233,8122030,0.99\n1320,Iron Maiden,104,1,1,,494602,11874875,0.99\n1321,Hallowed Be Thy Name,104,1,1,,447791,10751410,0.99\n1322,The Trooper,104,1,1,,232672,5588560,0.99\n1323,Sanctuary,104,1,1,,318511,7648679,0.99\n1324,Running Free,104,1,1,,474017,11380851,0.99\n1325,Tailgunner,105,1,3,Bruce Dickinson/Steve Harris,255582,4089856,0.99\n1326,Holy Smoke,105,1,3,Bruce Dickinson/Steve Harris,229459,3672064,0.99\n1327,No Prayer For The Dying,105,1,3,Steve Harris,263941,4225024,0.99\n1328,Public Enema Number One,105,1,3,Bruce Dickinson/David Murray,254197,4071587,0.99\n1329,Fates Warning,105,1,3,David Murray/Steve Harris,250853,4018088,0.99\n1330,The Assassin,105,1,3,Steve Harris,258768,4141056,0.99\n1331,Run Silent Run Deep,105,1,3,Bruce Dickinson/Steve Harris,275408,4407296,0.99\n1332,Hooks In You,105,1,3,Adrian Smith/Bruce Dickinson,247510,3960832,0.99\n1333,Bring Your Daughter... ...To The Slaughter,105,1,3,Bruce Dickinson,284238,4548608,0.99\n1334,Mother Russia,105,1,3,Steve Harris,332617,5322752,0.99\n1335,Where Eagles Dare,106,1,3,Steve Harris,369554,5914624,0.99\n1336,Revelations,106,1,3,Bruce Dickinson,408607,6539264,0.99\n1337,Flight Of The Icarus,106,1,3,Adrian Smith/Bruce Dickinson,230269,3686400,0.99\n1338,Die With Your Boots On,106,1,3,Adrian Smith/Bruce Dickinson/Steve Harris,325694,5212160,0.99\n1339,The Trooper,106,1,3,Steve Harris,251454,4024320,0.99\n1340,Still Life,106,1,3,David Murray/Steve Harris,294347,4710400,0.99\n1341,Quest For Fire,106,1,3,Steve Harris,221309,3543040,0.99\n1342,Sun And Steel,106,1,3,Adrian Smith/Bruce Dickinson,206367,3306324,0.99\n1343,To Tame A Land,106,1,3,Steve Harris,445283,7129264,0.99\n1344,Aces High,107,1,3,Harris,269531,6472088,0.99\n1345,2 Minutes To Midnight,107,1,3,Smith/Dickinson,359810,8638809,0.99\n1346,Losfer Words,107,1,3,Steve Harris,252891,6074756,0.99\n1347,Flash of The Blade,107,1,3,Dickinson,242729,5828861,0.99\n1348,Duelists,107,1,3,Steve Harris,366471,8800686,0.99\n1349,Back in the Village,107,1,3,Dickinson/Smith,320548,7696518,0.99\n1350,Powerslave,107,1,3,Dickinson,407823,9791106,0.99\n1351,Rime of the Ancient Mariner,107,1,3,Harris,816509,19599577,0.99\n1352,Intro,108,1,3,,115931,4638848,0.99\n1353,The Wicker Man,108,1,3,Adrian Smith/Bruce Dickinson/Steve Harris,281782,11272320,0.99\n1354,Ghost Of The Navigator,108,1,3,Bruce Dickinson/Janick Gers/Steve Harris,408607,16345216,0.99\n1355,Brave New World,108,1,3,Bruce Dickinson/David Murray/Steve Harris,366785,14676148,0.99\n1356,Wrathchild,108,1,3,Steve Harris,185808,7434368,0.99\n1357,2 Minutes To Midnight,108,1,3,Adrian Smith/Bruce Dickinson,386821,15474816,0.99\n1358,Blood Brothers,108,1,3,Steve Harris,435513,17422464,0.99\n1359,Sign Of The Cross,108,1,3,Steve Harris,649116,25966720,0.99\n1360,The Mercenary,108,1,3,Janick Gers/Steve Harris,282697,11309184,0.99\n1361,The Trooper,108,1,3,Steve Harris,273528,10942592,0.99\n1362,Dream Of Mirrors,109,1,1,Janick Gers/Steve Harris,578324,23134336,0.99\n1363,The Clansman,109,1,1,Steve Harris,559203,22370432,0.99\n1364,The Evil That Men Do,109,1,3,Adrian Smith/Bruce Dickinson/Steve Harris,280737,11231360,0.99\n1365,Fear Of The Dark,109,1,1,Steve Harris,460695,18430080,0.99\n1366,Iron Maiden,109,1,1,Steve Harris,351869,14076032,0.99\n1367,The Number Of The Beast,109,1,1,Steve Harris,300434,12022107,0.99\n1368,Hallowed Be Thy Name,109,1,1,Steve Harris,443977,17760384,0.99\n1369,Sanctuary,109,1,1,David Murray/Paul Di'Anno/Steve Harris,317335,12695680,0.99\n1370,Run To The Hills,109,1,1,Steve Harris,292179,11688064,0.99\n1371,Moonchild,110,1,3,Adrian Smith; Bruce Dickinson,340767,8179151,0.99\n1372,Infinite Dreams,110,1,3,Steve Harris,369005,8858669,0.99\n1373,Can I Play With Madness,110,1,3,Adrian Smith; Bruce Dickinson; Steve Harris,211043,5067867,0.99\n1374,The Evil That Men Do,110,1,3,Adrian Smith; Bruce Dickinson; Steve Harris,273998,6578930,0.99\n1375,Seventh Son of a Seventh Son,110,1,3,Steve Harris,593580,14249000,0.99\n1376,The Prophecy,110,1,3,Dave Murray; Steve Harris,305475,7334450,0.99\n1377,The Clairvoyant,110,1,3,Adrian Smith; Bruce Dickinson; Steve Harris,267023,6411510,0.99\n1378,Only the Good Die Young,110,1,3,Bruce Dickinson; Harris,280894,6744431,0.99\n1379,Caught Somewhere in Time,111,1,3,Steve Harris,445779,10701149,0.99\n1380,Wasted Years,111,1,3,Adrian Smith,307565,7384358,0.99\n1381,Sea of Madness,111,1,3,Adrian Smith,341995,8210695,0.99\n1382,Heaven Can Wait,111,1,3,Steve Harris,441417,10596431,0.99\n1383,Stranger in a Strange Land,111,1,3,Adrian Smith,344502,8270899,0.99\n1384,Alexander the Great,111,1,3,Steve Harris,515631,12377742,0.99\n1385,De Ja Vu,111,1,3,David Murray/Steve Harris,296176,7113035,0.99\n1386,The Loneliness of the Long Dis,111,1,3,Steve Harris,391314,9393598,0.99\n1387,22 Acacia Avenue,112,1,3,Adrian Smith/Steve Harris,395572,5542516,0.99\n1388,Children of the Damned,112,1,3,Steve Harris,274364,3845631,0.99\n1389,Gangland,112,1,3,Adrian Smith/Clive Burr/Steve Harris,228440,3202866,0.99\n1390,Hallowed Be Thy Name,112,1,3,Steve Harris,428669,6006107,0.99\n1391,Invaders,112,1,3,Steve Harris,203180,2849181,0.99\n1392,Run to the Hills,112,1,3,Steve Harris,228884,3209124,0.99\n1393,The Number Of The Beast,112,1,1,Steve Harris,293407,11737216,0.99\n1394,The Prisoner,112,1,3,Adrian Smith/Steve Harris,361299,5062906,0.99\n1395,Sign Of The Cross,113,1,1,Steve Harris,678008,27121792,0.99\n1396,Lord Of The Flies,113,1,1,Janick Gers/Steve Harris,303699,12148864,0.99\n1397,Man On The Edge,113,1,1,Blaze Bayley/Janick Gers,253413,10137728,0.99\n1398,Fortunes Of War,113,1,1,Steve Harris,443977,17760384,0.99\n1399,Look For The Truth,113,1,1,Blaze Bayley/Janick Gers/Steve Harris,310230,12411008,0.99\n1400,The Aftermath,113,1,1,Blaze Bayley/Janick Gers/Steve Harris,380786,15233152,0.99\n1401,Judgement Of Heaven,113,1,1,Steve Harris,312476,12501120,0.99\n1402,Blood On The World's Hands,113,1,1,Steve Harris,357799,14313600,0.99\n1403,The Edge Of Darkness,113,1,1,Blaze Bayley/Janick Gers/Steve Harris,399333,15974528,0.99\n1404,2 A.M.,113,1,1,Blaze Bayley/Janick Gers/Steve Harris,337658,13511087,0.99\n1405,The Unbeliever,113,1,1,Janick Gers/Steve Harris,490422,19617920,0.99\n1406,Futureal,114,1,1,Blaze Bayley/Steve Harris,175777,7032960,0.99\n1407,The Angel And The Gambler,114,1,1,Steve Harris,592744,23711872,0.99\n1408,Lightning Strikes Twice,114,1,1,David Murray/Steve Harris,290377,11616384,0.99\n1409,The Clansman,114,1,1,Steve Harris,539689,21592327,0.99\n1410,When Two Worlds Collide,114,1,1,Blaze Bayley/David Murray/Steve Harris,377312,15093888,0.99\n1411,The Educated Fool,114,1,1,Steve Harris,404767,16191616,0.99\n1412,Don't Look To The Eyes Of A Stranger,114,1,1,Steve Harris,483657,19347584,0.99\n1413,Como Estais Amigos,114,1,1,Blaze Bayley/Janick Gers,330292,13213824,0.99\n1414,Please Please Please,115,1,14,James Brown/Johnny Terry,165067,5394585,0.99\n1415,Think,115,1,14,Lowman Pauling,166739,5513208,0.99\n1416,Night Train,115,1,14,Jimmy Forrest/Lewis C. Simpkins/Oscar Washington,212401,7027377,0.99\n1417,Out Of Sight,115,1,14,Ted Wright,143725,4711323,0.99\n1418,Papa's Got A Brand New Bag Pt.1,115,1,14,James Brown,127399,4174420,0.99\n1419,I Got You (I Feel Good),115,1,14,James Brown,167392,5468472,0.99\n1420,It's A Man's Man's Man's World,115,1,14,Betty Newsome/James Brown,168228,5541611,0.99\n1421,Cold Sweat,115,1,14,Alfred Ellis/James Brown,172408,5643213,0.99\n1422,\"Say It Loud, I'm Black And I'm Proud Pt.1\",115,1,14,Alfred Ellis/James Brown,167392,5478117,0.99\n1423,Get Up (I Feel Like Being A) Sex Machine,115,1,14,Bobby Byrd/James Brown/Ron Lenhoff,316551,10498031,0.99\n1424,Hey America,115,1,14,Addie William Jones/Nat Jones,218226,7187857,0.99\n1425,Make It Funky Pt.1,115,1,14,Charles Bobbitt/James Brown,196231,6507782,0.99\n1426,I'm A Greedy Man Pt.1,115,1,14,Charles Bobbitt/James Brown,217730,7251211,0.99\n1427,Get On The Good Foot,115,1,14,Fred Wesley/James Brown/Joseph Mims,215902,7182736,0.99\n1428,Get Up Offa That Thing,115,1,14,Deanna Brown/Deidra Jenkins/Yamma Brown,250723,8355989,0.99\n1429,It's Too Funky In Here,115,1,14,Brad Shapiro/George Jackson/Robert Miller/Walter Shaw,239072,7973979,0.99\n1430,Living In America,115,1,14,Charlie Midnight/Dan Hartman,282880,9432346,0.99\n1431,I'm Real,115,1,14,Full Force/James Brown,334236,11183457,0.99\n1432,Hot Pants Pt.1,115,1,14,Fred Wesley/James Brown,188212,6295110,0.99\n1433,Soul Power (Live),115,1,14,James Brown,260728,8593206,0.99\n1434,When You Gonna Learn (Digeridoo),116,1,1,\"Jay Kay/Kay, Jay\",230635,7655482,0.99\n1435,Too Young To Die,116,1,1,\"Smith, Toby\",365818,12391660,0.99\n1436,Hooked Up,116,1,1,\"Smith, Toby\",275879,9301687,0.99\n1437,\"If I Like It, I Do It\",116,1,1,\"Gelder, Nick van\",293093,9848207,0.99\n1438,Music Of The Wind,116,1,1,\"Smith, Toby\",383033,12870239,0.99\n1439,Emergency On Planet Earth,116,1,1,\"Smith, Toby\",245263,8117218,0.99\n1440,\"Whatever It Is, I Just Can't Stop\",116,1,1,\"Jay Kay/Kay, Jay\",247222,8249453,0.99\n1441,Blow Your Mind,116,1,1,\"Smith, Toby\",512339,17089176,0.99\n1442,Revolution 1993,116,1,1,\"Smith, Toby\",616829,20816872,0.99\n1443,Didgin' Out,116,1,1,\"Buchanan, Wallis\",157100,5263555,0.99\n1444,Canned Heat,117,1,14,Jay Kay,331964,11042037,0.99\n1445,Planet Home,117,1,14,Jay Kay/Toby Smith,284447,9566237,0.99\n1446,Black Capricorn Day,117,1,14,Jay Kay,341629,11477231,0.99\n1447,Soul Education,117,1,14,Jay Kay/Toby Smith,255477,8575435,0.99\n1448,Failling,117,1,14,Jay Kay/Toby Smith,225227,7503999,0.99\n1449,Destitute Illusions,117,1,14,Derrick McKenzie/Jay Kay/Toby Smith,340218,11452651,0.99\n1450,Supersonic,117,1,14,Jay Kay,315872,10699265,0.99\n1451,Butterfly,117,1,14,Jay Kay/Toby Smith,268852,8947356,0.99\n1452,Were Do We Go From Here,117,1,14,Jay Kay,313626,10504158,0.99\n1453,King For A Day,117,1,14,Jay Kay/Toby Smith,221544,7335693,0.99\n1454,Deeper Underground,117,1,14,Toby Smith,281808,9351277,0.99\n1455,Just Another Story,118,1,15,Toby Smith,529684,17582818,0.99\n1456,Stillness In Time,118,1,15,Toby Smith,257097,8644290,0.99\n1457,Half The Man,118,1,15,Toby Smith,289854,9577679,0.99\n1458,Light Years,118,1,15,Toby Smith,354560,11796244,0.99\n1459,Manifest Destiny,118,1,15,Toby Smith,382197,12676962,0.99\n1460,The Kids,118,1,15,Toby Smith,309995,10334529,0.99\n1461,Mr. Moon,118,1,15,Stuard Zender/Toby Smith,329534,11043559,0.99\n1462,Scam,118,1,15,Stuart Zender,422321,14019705,0.99\n1463,Journey To Arnhemland,118,1,15,Toby Smith/Wallis Buchanan,322455,10843832,0.99\n1464,Morning Glory,118,1,15,J. Kay/Jay Kay,384130,12777210,0.99\n1465,Space Cowboy,118,1,15,J. Kay/Jay Kay,385697,12906520,0.99\n1466,Last Chance,119,1,4,C. Cester/C. Muncey,112352,3683130,0.99\n1467,Are You Gonna Be My Girl,119,1,4,C. Muncey/N. Cester,213890,6992324,0.99\n1468,Rollover D.J.,119,1,4,C. Cester/N. Cester,196702,6406517,0.99\n1469,Look What You've Done,119,1,4,N. Cester,230974,7517083,0.99\n1470,Get What You Need,119,1,4,C. Cester/C. Muncey/N. Cester,247719,8043765,0.99\n1471,Move On,119,1,4,C. Cester/N. Cester,260623,8519353,0.99\n1472,Radio Song,119,1,4,C. Cester/C. Muncey/N. Cester,272117,8871509,0.99\n1473,Get Me Outta Here,119,1,4,C. Cester/N. Cester,176274,5729098,0.99\n1474,Cold Hard Bitch,119,1,4,C. Cester/C. Muncey/N. Cester,243278,7929610,0.99\n1475,Come Around Again,119,1,4,C. Muncey/N. Cester,270497,8872405,0.99\n1476,Take It Or Leave It,119,1,4,C. Muncey/N. Cester,142889,4643370,0.99\n1477,Lazy Gun,119,1,4,C. Cester/N. Cester,282174,9186285,0.99\n1478,Timothy,119,1,4,C. Cester,270341,8856507,0.99\n1479,Foxy Lady,120,1,1,Jimi Hendrix,199340,6480896,0.99\n1480,Manic Depression,120,1,1,Jimi Hendrix,222302,7289272,0.99\n1481,Red House,120,1,1,Jimi Hendrix,224130,7285851,0.99\n1482,Can You See Me,120,1,1,Jimi Hendrix,153077,4987068,0.99\n1483,Love Or Confusion,120,1,1,Jimi Hendrix,193123,6329408,0.99\n1484,I Don't Live Today,120,1,1,Jimi Hendrix,235311,7661214,0.99\n1485,May This Be Love,120,1,1,Jimi Hendrix,191216,6240028,0.99\n1486,Fire,120,1,1,Jimi Hendrix,164989,5383075,0.99\n1487,Third Stone From The Sun,120,1,1,Jimi Hendrix,404453,13186975,0.99\n1488,Remember,120,1,1,Jimi Hendrix,168150,5509613,0.99\n1489,Are You Experienced?,120,1,1,Jimi Hendrix,254537,8292497,0.99\n1490,Hey Joe,120,1,1,Billy Roberts,210259,6870054,0.99\n1491,Stone Free,120,1,1,Jimi Hendrix,216293,7002331,0.99\n1492,Purple Haze,120,1,1,Jimi Hendrix,171572,5597056,0.99\n1493,51st Anniversary,120,1,1,Jimi Hendrix,196388,6398044,0.99\n1494,The Wind Cries Mary,120,1,1,Jimi Hendrix,200463,6540638,0.99\n1495,Highway Chile,120,1,1,Jimi Hendrix,212453,6887949,0.99\n1496,Surfing with the Alien,121,2,1,,263707,4418504,0.99\n1497,Ice 9,121,2,1,,239721,4036215,0.99\n1498,Crushing Day,121,2,1,,314768,5232158,0.99\n1499,\"Always With Me, Always With You\",121,2,1,,202035,3435777,0.99\n1500,Satch Boogie,121,2,1,,193560,3300654,0.99\n1501,Hill of the Skull,121,2,1,J. Satriani,108435,1944738,0.99\n1502,Circles,121,2,1,,209071,3548553,0.99\n1503,Lords of Karma,121,2,1,J. Satriani,288227,4809279,0.99\n1504,Midnight,121,2,1,J. Satriani,102630,1851753,0.99\n1505,Echo,121,2,1,J. Satriani,337570,5595557,0.99\n1506,Engenho De Dentro,122,1,7,,310073,10211473,0.99\n1507,Alcohol,122,1,7,,355239,12010478,0.99\n1508,Mama Africa,122,1,7,,283062,9488316,0.99\n1509,Salve Simpatia,122,1,7,,343484,11314756,0.99\n1510,W/Brasil (Chama O Síndico),122,1,7,,317100,10599953,0.99\n1511,País Tropical,122,1,7,,452519,14946972,0.99\n1512,Os Alquimistas Estão Chegando,122,1,7,,367281,12304520,0.99\n1513,Charles Anjo 45,122,1,7,,389276,13022833,0.99\n1514,Selassiê,122,1,7,,326321,10724982,0.99\n1515,Menina Sarará,122,1,7,,191477,6393818,0.99\n1516,Que Maravilha,122,1,7,,338076,10996656,0.99\n1517,Santa Clara Clareou,122,1,7,,380081,12524725,0.99\n1518,Filho Maravilha,122,1,7,,227526,7498259,0.99\n1519,Taj Mahal,122,1,7,,289750,9502898,0.99\n1520,Rapidamente,123,1,7,,252238,8470107,0.99\n1521,As Dores do Mundo,123,1,7,Hyldon,255477,8537092,0.99\n1522,Vou Pra Ai,123,1,7,,300878,10053718,0.99\n1523,My Brother,123,1,7,,253231,8431821,0.99\n1524,Há Quanto Tempo,123,1,7,,270027,9004470,0.99\n1525,Vício,123,1,7,,269897,8887216,0.99\n1526,Encontrar Alguém,123,1,7,Marco Tulio Lara/Rogerio Flausino,224078,7437935,0.99\n1527,Dance Enquanto é Tempo,123,1,7,,229093,7583799,0.99\n1528,A Tarde,123,1,7,,266919,8836127,0.99\n1529,Always Be All Right,123,1,7,,128078,4299676,0.99\n1530,Sem Sentido,123,1,7,,250462,8292108,0.99\n1531,Onibusfobia,123,1,7,,315977,10474904,0.99\n1532,Pura Elegancia,124,1,16,João Suplicy,284107,9632269,0.99\n1533,Choramingando,124,1,16,João Suplicy,190484,6400532,0.99\n1534,Por Merecer,124,1,16,João Suplicy,230582,7764601,0.99\n1535,No Futuro,124,1,16,João Suplicy,182308,6056200,0.99\n1536,Voce Inteira,124,1,16,João Suplicy,241084,8077282,0.99\n1537,Cuando A Noite Vai Chegando,124,1,16,João Suplicy,270628,9081874,0.99\n1538,Naquele Dia,124,1,16,João Suplicy,251768,8452654,0.99\n1539,Equinocio,124,1,16,João Suplicy,269008,8871455,0.99\n1540,Papelão,124,1,16,João Suplicy,213263,7257390,0.99\n1541,Cuando Eu For Pro Ceu,124,1,16,João Suplicy,118804,3948371,0.99\n1542,Do Nosso Amor,124,1,16,João Suplicy,203415,6774566,0.99\n1543,Borogodo,124,1,16,João Suplicy,208457,7104588,0.99\n1544,Cafezinho,124,1,16,João Suplicy,180924,6031174,0.99\n1545,Enquanto O Dia Não Vem,124,1,16,João Suplicy,220891,7248336,0.99\n1546,The Green Manalishi,125,1,3,,205792,6720789,0.99\n1547,Living After Midnight,125,1,3,,213289,7056785,0.99\n1548,Breaking The Law (Live),125,1,3,,144195,4728246,0.99\n1549,Hot Rockin',125,1,3,,197328,6509179,0.99\n1550,Heading Out To The Highway (Live),125,1,3,,276427,9006022,0.99\n1551,The Hellion,125,1,3,,41900,1351993,0.99\n1552,Electric Eye,125,1,3,,222197,7231368,0.99\n1553,You've Got Another Thing Comin',125,1,3,,305162,9962558,0.99\n1554,Turbo Lover,125,1,3,,335542,11068866,0.99\n1555,Freewheel Burning,125,1,3,,265952,8713599,0.99\n1556,Some Heads Are Gonna Roll,125,1,3,,249939,8198617,0.99\n1557,Metal Meltdown,125,1,3,,290664,9390646,0.99\n1558,Ram It Down,125,1,3,,292179,9554023,0.99\n1559,Diamonds And Rust (Live),125,1,3,,219350,7163147,0.99\n1560,Victim Of Change (Live),125,1,3,,430942,14067512,0.99\n1561,Tyrant (Live),125,1,3,,282253,9190536,0.99\n1562,Comin' Home,126,1,1,\"Paul Stanley, Ace Frehley\",172068,5661120,0.99\n1563,Plaster Caster,126,1,1,Gene Simmons,198060,6528719,0.99\n1564,Goin' Blind,126,1,1,\"Gene Simmons, Stephen Coronel\",217652,7167523,0.99\n1565,Do You Love Me,126,1,1,\"Paul Stanley, Bob Ezrin, Kim Fowley\",193619,6343111,0.99\n1566,Domino,126,1,1,Gene Simmons,226377,7488191,0.99\n1567,Sure Know Something,126,1,1,\"Paul Stanley, Vincent Poncia\",254354,8375190,0.99\n1568,A World Without Heroes,126,1,1,\"Paul Stanley, Gene Simmons, Bob Ezrin, Lewis Reed\",177815,5832524,0.99\n1569,Rock Bottom,126,1,1,\"Paul Stanley, Ace Frehley\",200594,6560818,0.99\n1570,See You Tonight,126,1,1,Gene Simmons,146494,4817521,0.99\n1571,I Still Love You,126,1,1,Paul Stanley,369815,12086145,0.99\n1572,Every Time I Look At You,126,1,1,\"Paul Stanley, Vincent Cusano\",283898,9290948,0.99\n1573,\"2,000 Man\",126,1,1,\"Mick Jagger, Keith Richard\",312450,10292829,0.99\n1574,Beth,126,1,1,\"Peter Criss, Stan Penridge, Bob Ezrin\",170187,5577807,0.99\n1575,Nothin' To Lose,126,1,1,Gene Simmons,222354,7351460,0.99\n1576,Rock And Roll All Nite,126,1,1,\"Paul Stanley, Gene Simmons\",259631,8549296,0.99\n1577,Immigrant Song,127,1,1,Robert Plant,201247,6457766,0.99\n1578,Heartbreaker,127,1,1,John Bonham/John Paul Jones/Robert Plant,316081,10179657,0.99\n1579,Since I've Been Loving You,127,1,1,John Paul Jones/Robert Plant,416365,13471959,0.99\n1580,Black Dog,127,1,1,John Paul Jones/Robert Plant,317622,10267572,0.99\n1581,Dazed And Confused,127,1,1,Jimmy Page/Led Zeppelin,1116734,36052247,0.99\n1582,Stairway To Heaven,127,1,1,Robert Plant,529658,17050485,0.99\n1583,Going To California,127,1,1,Robert Plant,234605,7646749,0.99\n1584,That's The Way,127,1,1,Robert Plant,343431,11248455,0.99\n1585,Whole Lotta Love (Medley),127,1,1,Arthur Crudup/Bernard Besman/Bukka White/Doc Pomus/John Bonham/John Lee Hooker/John Paul Jones/Mort Shuman/Robert Plant/Willie Dixon,825103,26742545,0.99\n1586,Thank You,127,1,1,Robert Plant,398262,12831826,0.99\n1587,We're Gonna Groove,128,1,1,Ben E.King/James Bethea,157570,5180975,0.99\n1588,Poor Tom,128,1,1,Jimmy Page/Robert Plant,182491,6016220,0.99\n1589,I Can't Quit You Baby,128,1,1,Willie Dixon,258168,8437098,0.99\n1590,Walter's Walk,128,1,1,\"Jimmy Page, Robert Plant\",270785,8712499,0.99\n1591,Ozone Baby,128,1,1,\"Jimmy Page, Robert Plant\",215954,7079588,0.99\n1592,Darlene,128,1,1,\"Jimmy Page, Robert Plant, John Bonham, John Paul Jones\",307226,10078197,0.99\n1593,Bonzo's Montreux,128,1,1,John Bonham,258925,8557447,0.99\n1594,Wearing And Tearing,128,1,1,\"Jimmy Page, Robert Plant\",330004,10701590,0.99\n1595,The Song Remains The Same,129,1,1,Jimmy Page/Jimmy Page & Robert Plant/Robert Plant,330004,10708950,0.99\n1596,The Rain Song,129,1,1,Jimmy Page/Jimmy Page & Robert Plant/Robert Plant,459180,15029875,0.99\n1597,Over The Hills And Far Away,129,1,1,Jimmy Page/Jimmy Page & Robert Plant/Robert Plant,290089,9552829,0.99\n1598,The Crunge,129,1,1,John Bonham/John Paul Jones,197407,6460212,0.99\n1599,Dancing Days,129,1,1,Jimmy Page/Jimmy Page & Robert Plant/Robert Plant,223216,7250104,0.99\n1600,D'Yer Mak'er,129,1,1,John Bonham/John Paul Jones,262948,8645935,0.99\n1601,No Quarter,129,1,1,John Paul Jones,420493,13656517,0.99\n1602,The Ocean,129,1,1,John Bonham/John Paul Jones,271098,8846469,0.99\n1603,In The Evening,130,1,1,\"Jimmy Page, Robert Plant & John Paul Jones\",410566,13399734,0.99\n1604,South Bound Saurez,130,1,1,John Paul Jones & Robert Plant,254406,8420427,0.99\n1605,Fool In The Rain,130,1,1,\"Jimmy Page, Robert Plant & John Paul Jones\",372950,12371433,0.99\n1606,Hot Dog,130,1,1,Jimmy Page & Robert Plant,197198,6536167,0.99\n1607,Carouselambra,130,1,1,\"John Paul Jones, Jimmy Page & Robert Plant\",634435,20858315,0.99\n1608,All My Love,130,1,1,Robert Plant & John Paul Jones,356284,11684862,0.99\n1609,I'm Gonna Crawl,130,1,1,\"Jimmy Page, Robert Plant & John Paul Jones\",329639,10737665,0.99\n1610,Black Dog,131,1,1,\"Jimmy Page, Robert Plant, John Paul Jones\",296672,9660588,0.99\n1611,Rock & Roll,131,1,1,\"Jimmy Page, Robert Plant, John Paul Jones, John Bonham\",220917,7142127,0.99\n1612,The Battle Of Evermore,131,1,1,\"Jimmy Page, Robert Plant\",351555,11525689,0.99\n1613,Stairway To Heaven,131,1,1,\"Jimmy Page, Robert Plant\",481619,15706767,0.99\n1614,Misty Mountain Hop,131,1,1,\"Jimmy Page, Robert Plant, John Paul Jones\",278857,9092799,0.99\n1615,Four Sticks,131,1,1,\"Jimmy Page, Robert Plant\",284447,9481301,0.99\n1616,Going To California,131,1,1,\"Jimmy Page, Robert Plant\",215693,7068737,0.99\n1617,When The Levee Breaks,131,1,1,\"Jimmy Page, Robert Plant, John Paul Jones, John Bonham, Memphis Minnie\",427702,13912107,0.99\n1618,Good Times Bad Times,132,1,1,Jimmy Page/John Bonham/John Paul Jones,166164,5464077,0.99\n1619,Babe I'm Gonna Leave You,132,1,1,Jimmy Page/Robert Plant,401475,13189312,0.99\n1620,You Shook Me,132,1,1,J. B. Lenoir/Willie Dixon,388179,12643067,0.99\n1621,Dazed and Confused,132,1,1,Jimmy Page,386063,12610326,0.99\n1622,Your Time Is Gonna Come,132,1,1,Jimmy Page/John Paul Jones,274860,9011653,0.99\n1623,Black Mountain Side,132,1,1,Jimmy Page,132702,4440602,0.99\n1624,Communication Breakdown,132,1,1,Jimmy Page/John Bonham/John Paul Jones,150230,4899554,0.99\n1625,I Can't Quit You Baby,132,1,1,Willie Dixon,282671,9252733,0.99\n1626,How Many More Times,132,1,1,Jimmy Page/John Bonham/John Paul Jones,508055,16541364,0.99\n1627,Whole Lotta Love,133,1,1,\"Jimmy Page, Robert Plant, John Paul Jones, John Bonham\",334471,11026243,0.99\n1628,What Is And What Should Never Be,133,1,1,\"Jimmy Page, Robert Plant\",287973,9369385,0.99\n1629,The Lemon Song,133,1,1,\"Jimmy Page, Robert Plant, John Paul Jones, John Bonham\",379141,12463496,0.99\n1630,Thank You,133,1,1,\"Jimmy Page, Robert Plant\",287791,9337392,0.99\n1631,Heartbreaker,133,1,1,\"Jimmy Page, Robert Plant, John Paul Jones, John Bonham\",253988,8387560,0.99\n1632,Living Loving Maid (She's Just A Woman),133,1,1,\"Jimmy Page, Robert Plant\",159216,5219819,0.99\n1633,Ramble On,133,1,1,\"Jimmy Page, Robert Plant\",275591,9199710,0.99\n1634,Moby Dick,133,1,1,\"John Bonham, John Paul Jones, Jimmy Page\",260728,8664210,0.99\n1635,Bring It On Home,133,1,1,\"Jimmy Page, Robert Plant\",259970,8494731,0.99\n1636,Immigrant Song,134,1,1,\"Jimmy Page, Robert Plant\",144875,4786461,0.99\n1637,Friends,134,1,1,\"Jimmy Page, Robert Plant\",233560,7694220,0.99\n1638,Celebration Day,134,1,1,\"Jimmy Page, Robert Plant, John Paul Jones\",209528,6871078,0.99\n1639,Since I've Been Loving You,134,1,1,\"Jimmy Page, Robert Plant, John Paul Jones\",444055,14482460,0.99\n1640,Out On The Tiles,134,1,1,\"Jimmy Page, Robert Plant, John Bonham\",246047,8060350,0.99\n1641,Gallows Pole,134,1,1,Traditional,296228,9757151,0.99\n1642,Tangerine,134,1,1,Jimmy Page,189675,6200893,0.99\n1643,That's The Way,134,1,1,\"Jimmy Page, Robert Plant\",337345,11202499,0.99\n1644,Bron-Y-Aur Stomp,134,1,1,\"Jimmy Page, Robert Plant, John Paul Jones\",259500,8674508,0.99\n1645,Hats Off To (Roy) Harper,134,1,1,Traditional,219376,7236640,0.99\n1646,In The Light,135,1,1,John Paul Jones/Robert Plant,526785,17033046,0.99\n1647,Bron-Yr-Aur,135,1,1,Jimmy Page,126641,4150746,0.99\n1648,Down By The Seaside,135,1,1,Robert Plant,316186,10371282,0.99\n1649,Ten Years Gone,135,1,1,Robert Plant,393116,12756366,0.99\n1650,Night Flight,135,1,1,John Paul Jones/Robert Plant,217547,7160647,0.99\n1651,The Wanton Song,135,1,1,Robert Plant,249887,8180988,0.99\n1652,Boogie With Stu,135,1,1,Ian Stewart/John Bonham/John Paul Jones/Mrs. Valens/Robert Plant,233273,7657086,0.99\n1653,Black Country Woman,135,1,1,Robert Plant,273084,8951732,0.99\n1654,Sick Again,135,1,1,Robert Plant,283036,9279263,0.99\n1655,Achilles Last Stand,136,1,1,Jimmy Page/Robert Plant,625502,20593955,0.99\n1656,For Your Life,136,1,1,Jimmy Page/Robert Plant,384391,12633382,0.99\n1657,Royal Orleans,136,1,1,John Bonham/John Paul Jones,179591,5930027,0.99\n1658,Nobody's Fault But Mine,136,1,1,Jimmy Page/Robert Plant,376215,12237859,0.99\n1659,Candy Store Rock,136,1,1,Jimmy Page/Robert Plant,252055,8397423,0.99\n1660,Hots On For Nowhere,136,1,1,Jimmy Page/Robert Plant,284107,9342342,0.99\n1661,Tea For One,136,1,1,Jimmy Page/Robert Plant,566752,18475264,0.99\n1662,Rock & Roll,137,1,1,John Bonham/John Paul Jones/Robert Plant,242442,7897065,0.99\n1663,Celebration Day,137,1,1,John Paul Jones/Robert Plant,230034,7478487,0.99\n1664,The Song Remains The Same,137,1,1,Robert Plant,353358,11465033,0.99\n1665,Rain Song,137,1,1,Robert Plant,505808,16273705,0.99\n1666,Dazed And Confused,137,1,1,Jimmy Page,1612329,52490554,0.99\n1667,No Quarter,138,1,1,John Paul Jones/Robert Plant,749897,24399285,0.99\n1668,Stairway To Heaven,138,1,1,Robert Plant,657293,21354766,0.99\n1669,Moby Dick,138,1,1,John Bonham/John Paul Jones,766354,25345841,0.99\n1670,Whole Lotta Love,138,1,1,John Bonham/John Paul Jones/Robert Plant/Willie Dixon,863895,28191437,0.99\n1671,Natália,139,1,7,Renato Russo,235728,7640230,0.99\n1672,L'Avventura,139,1,7,Renato Russo,278256,9165769,0.99\n1673,Música De Trabalho,139,1,7,Renato Russo,260231,8590671,0.99\n1674,Longe Do Meu Lado,139,1,7,Renato Russo - Marcelo Bonfá,266161,8655249,0.99\n1675,A Via Láctea,139,1,7,Renato Russo,280084,9234879,0.99\n1676,Música Ambiente,139,1,7,Renato Russo,247614,8234388,0.99\n1677,Aloha,139,1,7,Renato Russo,325955,10793301,0.99\n1678,Soul Parsifal,139,1,7,Renato Russo - Marisa Monte,295053,9853589,0.99\n1679,Dezesseis,139,1,7,Renato Russo,323918,10573515,0.99\n1680,Mil Pedaços,139,1,7,Renato Russo,203337,6643291,0.99\n1681,Leila,139,1,7,Renato Russo,323056,10608239,0.99\n1682,1º De Julho,139,1,7,Renato Russo,290298,9619257,0.99\n1683,Esperando Por Mim,139,1,7,Renato Russo,261668,8844133,0.99\n1684,Quando Você Voltar,139,1,7,Renato Russo,173897,5781046,0.99\n1685,O Livro Dos Dias,139,1,7,Renato Russo,257253,8570929,0.99\n1686,Será,140,1,7,Dado Villa-Lobos/Marcelo Bonfá,148401,4826528,0.99\n1687,Ainda É Cedo,140,1,7,Dado Villa-Lobos/Ico Ouro-Preto/Marcelo Bonfá,236826,7796400,0.99\n1688,Geração Coca-Cola,140,1,7,Renato Russo,141453,4625731,0.99\n1689,Eduardo E Mônica,140,1,7,Renato Russo,271229,9026691,0.99\n1690,Tempo Perdido,140,1,7,Renato Russo,302158,9963914,0.99\n1691,Indios,140,1,7,Renato Russo,258168,8610226,0.99\n1692,Que País É Este,140,1,7,Renato Russo,177606,5822124,0.99\n1693,Faroeste Caboclo,140,1,7,Renato Russo,543007,18092739,0.99\n1694,Há Tempos,140,1,7,Dado Villa-Lobos/Marcelo Bonfá,197146,6432922,0.99\n1695,Pais E Filhos,140,1,7,Dado Villa-Lobos/Marcelo Bonfá,308401,10130685,0.99\n1696,Meninos E Meninas,140,1,7,Dado Villa-Lobos/Marcelo Bonfá,203781,6667802,0.99\n1697,Vento No Litoral,140,1,7,Dado Villa-Lobos/Marcelo Bonfá,366445,12063806,0.99\n1698,Perfeição,140,1,7,Dado Villa-Lobos/Marcelo Bonfá,276558,9258489,0.99\n1699,Giz,140,1,7,Dado Villa-Lobos/Marcelo Bonfá,202213,6677671,0.99\n1700,Dezesseis,140,1,7,Dado Villa-Lobos/Marcelo Bonfá,321724,10501773,0.99\n1701,Antes Das Seis,140,1,7,Dado Villa-Lobos,189231,6296531,0.99\n1702,Are You Gonna Go My Way,141,1,1,Craig Ross/Lenny Kravitz,211591,6905135,0.99\n1703,Fly Away,141,1,1,Lenny Kravitz,221962,7322085,0.99\n1704,Rock And Roll Is Dead,141,1,1,Lenny Kravitz,204199,6680312,0.99\n1705,Again,141,1,1,Lenny Kravitz,228989,7490476,0.99\n1706,It Ain't Over 'Til It's Over,141,1,1,Lenny Kravitz,242703,8078936,0.99\n1707,Can't Get You Off My Mind,141,1,1,Lenny Kravitz,273815,8937150,0.99\n1708,Mr. Cab Driver,141,1,1,Lenny Kravitz,230321,7668084,0.99\n1709,American Woman,141,1,1,B. Cummings/G. Peterson/M.J. Kale/R. Bachman,261773,8538023,0.99\n1710,Stand By My Woman,141,1,1,Henry Kirssch/Lenny Kravitz/S. Pasch A. Krizan,259683,8447611,0.99\n1711,Always On The Run,141,1,1,Lenny Kravitz/Slash,232515,7593397,0.99\n1712,Heaven Help,141,1,1,Gerry DeVeaux/Terry Britten,190354,6222092,0.99\n1713,I Belong To You,141,1,1,Lenny Kravitz,257123,8477980,0.99\n1714,Believe,141,1,1,Henry Hirsch/Lenny Kravitz,295131,9661978,0.99\n1715,Let Love Rule,141,1,1,Lenny Kravitz,342648,11298085,0.99\n1716,Black Velveteen,141,1,1,Lenny Kravitz,290899,9531301,0.99\n1717,Assim Caminha A Humanidade,142,1,7,,210755,6993763,0.99\n1718,Honolulu,143,1,7,,261433,8558481,0.99\n1719,Dancin´Days,143,1,7,,237400,7875347,0.99\n1720,Um Pro Outro,142,1,7,,236382,7825215,0.99\n1721,Aviso Aos Navegantes,143,1,7,,242808,8058651,0.99\n1722,Casa,142,1,7,,307591,10107269,0.99\n1723,Condição,142,1,7,,263549,8778465,0.99\n1724,Hyperconectividade,143,1,7,,180636,5948039,0.99\n1725,O Descobridor Dos Sete Mares,143,1,7,,225854,7475780,0.99\n1726,Satisfação,142,1,7,,208065,6901681,0.99\n1727,Brumário,142,1,7,,216241,7243499,0.99\n1728,Um Certo Alguém,143,1,7,,194063,6430939,0.99\n1729,Fullgás,143,1,7,,346070,11505484,0.99\n1730,Sábado À Noite,142,1,7,,193854,6435114,0.99\n1731,A Cura,142,1,7,,280920,9260588,0.99\n1732,Aquilo,143,1,7,,246073,8167819,0.99\n1733,Atrás Do Trio Elétrico,142,1,7,,149080,4917615,0.99\n1734,Senta A Pua,143,1,7,,217547,7205844,0.99\n1735,Ro-Que-Se-Da-Ne,143,1,7,,146703,4805897,0.99\n1736,Tudo Bem,142,1,7,,196101,6419139,0.99\n1737,Toda Forma De Amor,142,1,7,,227813,7496584,0.99\n1738,Tudo Igual,143,1,7,,276035,9201645,0.99\n1739,Fogo De Palha,143,1,7,,246804,8133732,0.99\n1740,Sereia,142,1,7,,278047,9121087,0.99\n1741,Assaltaram A Gramática,143,1,7,,261041,8698959,0.99\n1742,Se Você Pensa,142,1,7,,195996,6552490,0.99\n1743,Lá Vem O Sol (Here Comes The Sun),142,1,7,,189492,6229645,0.99\n1744,O Último Romântico (Ao Vivo),143,1,7,,231993,7692697,0.99\n1745,Pseudo Silk Kimono,144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",134739,4334038,0.99\n1746,Kayleigh,144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",234605,7716005,0.99\n1747,Lavender,144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",153417,4999814,0.99\n1748,Bitter Suite: Brief Encounter / Lost Weekend / Blue Angel,144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",356493,11791068,0.99\n1749,Heart Of Lothian: Wide Boy / Curtain Call,144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",366053,11893723,0.99\n1750,Waterhole (Expresso Bongo),144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",133093,4378835,0.99\n1751,Lords Of The Backstage,144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",112875,3741319,0.99\n1752,Blind Curve: Vocal Under A Bloodlight / Passing Strangers / Mylo / Perimeter Walk / Threshold,144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",569704,18578995,0.99\n1753,Childhoods End?,144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",272796,9015366,0.99\n1754,White Feather,144,1,1,\"Kelly, Mosley, Rothery, Trewaves\",143595,4711776,0.99\n1755,Arrepio,145,1,7,Carlinhos Brown,136254,4511390,0.99\n1756,Magamalabares,145,1,7,Carlinhos Brown,215875,7183757,0.99\n1757,Chuva No Brejo,145,1,7,Morais,145606,4857761,0.99\n1758,Cérebro Eletrônico,145,1,7,Gilberto Gil,172800,5760864,0.99\n1759,Tempos Modernos,145,1,7,Lulu Santos,183066,6066234,0.99\n1760,Maraçá,145,1,7,Carlinhos Brown,230008,7621482,0.99\n1761,Blanco,145,1,7,Marisa Monte/poema de Octavio Paz/versão: Haroldo de Campos,45191,1454532,0.99\n1762,Panis Et Circenses,145,1,7,Caetano Veloso e Gilberto Gil,192339,6318373,0.99\n1763,De Noite Na Cama,145,1,7,Caetano Veloso e Gilberto Gil,209005,7012658,0.99\n1764,Beija Eu,145,1,7,Caetano Veloso e Gilberto Gil,197276,6512544,0.99\n1765,Give Me Love,145,1,7,Caetano Veloso e Gilberto Gil,249808,8196331,0.99\n1766,Ainda Lembro,145,1,7,Caetano Veloso e Gilberto Gil,218801,7211247,0.99\n1767,A Menina Dança,145,1,7,Caetano Veloso e Gilberto Gil,129410,4326918,0.99\n1768,Dança Da Solidão,145,1,7,Caetano Veloso e Gilberto Gil,203520,6699368,0.99\n1769,Ao Meu Redor,145,1,7,Caetano Veloso e Gilberto Gil,275591,9158834,0.99\n1770,Bem Leve,145,1,7,Caetano Veloso e Gilberto Gil,159190,5246835,0.99\n1771,Segue O Seco,145,1,7,Caetano Veloso e Gilberto Gil,178207,5922018,0.99\n1772,O Xote Das Meninas,145,1,7,Caetano Veloso e Gilberto Gil,291866,9553228,0.99\n1773,Wherever I Lay My Hat,146,1,14,,136986,4477321,0.99\n1774,Get My Hands On Some Lovin',146,1,14,,149054,4860380,0.99\n1775,No Good Without You,146,1,14,\"William \"\"Mickey\"\" Stevenson\",161410,5259218,0.99\n1776,You've Been A Long Time Coming,146,1,14,Brian Holland/Eddie Holland/Lamont Dozier,137221,4437949,0.99\n1777,When I Had Your Love,146,1,14,\"Robert Rogers/Warren \"\"Pete\"\" Moore/William \"\"Mickey\"\" Stevenson\",152424,4972815,0.99\n1778,You're What's Happening (In The World Today),146,1,14,Allen Story/George Gordy/Robert Gordy,142027,4631104,0.99\n1779,Loving You Is Sweeter Than Ever,146,1,14,Ivy Hunter/Stevie Wonder,166295,5377546,0.99\n1780,It's A Bitter Pill To Swallow,146,1,14,\"Smokey Robinson/Warren \"\"Pete\"\" Moore\",194821,6477882,0.99\n1781,Seek And You Shall Find,146,1,14,\"Ivy Hunter/William \"\"Mickey\"\" Stevenson\",223451,7306719,0.99\n1782,Gonna Keep On Tryin' Till I Win Your Love,146,1,14,Barrett Strong/Norman Whitfield,176404,5789945,0.99\n1783,Gonna Give Her All The Love I've Got,146,1,14,Barrett Strong/Norman Whitfield,210886,6893603,0.99\n1784,I Wish It Would Rain,146,1,14,Barrett Strong/Norman Whitfield/Roger Penzabene,172486,5647327,0.99\n1785,\"Abraham, Martin And John\",146,1,14,Dick Holler,273057,8888206,0.99\n1786,Save The Children,146,1,14,Al Cleveland/Marvin Gaye/Renaldo Benson,194821,6342021,0.99\n1787,You Sure Love To Ball,146,1,14,Marvin Gaye,218540,7217872,0.99\n1788,Ego Tripping Out,146,1,14,Marvin Gaye,314514,10383887,0.99\n1789,Praise,146,1,14,Marvin Gaye,235833,7839179,0.99\n1790,Heavy Love Affair,146,1,14,Marvin Gaye,227892,7522232,0.99\n1791,Down Under,147,1,1,,222171,7366142,0.99\n1792,Overkill,147,1,1,,225410,7408652,0.99\n1793,Be Good Johnny,147,1,1,,216320,7139814,0.99\n1794,Everything I Need,147,1,1,,216476,7107625,0.99\n1795,Down by the Sea,147,1,1,,408163,13314900,0.99\n1796,Who Can It Be Now?,147,1,1,,202396,6682850,0.99\n1797,It's a Mistake,147,1,1,,273371,8979965,0.99\n1798,Dr. Heckyll & Mr. Jive,147,1,1,,278465,9110403,0.99\n1799,Shakes and Ladders,147,1,1,,198008,6560753,0.99\n1800,No Sign of Yesterday,147,1,1,,362004,11829011,0.99\n1801,Enter Sandman,148,1,3,\"James Hetfield, Lars Ulrich and Kirk Hammett\",332251,10852002,0.99\n1802,Sad But True,148,1,3,Ulrich,324754,10541258,0.99\n1803,Holier Than Thou,148,1,3,Ulrich,227892,7462011,0.99\n1804,The Unforgiven,148,1,3,\"James Hetfield, Lars Ulrich and Kirk Hammett\",387082,12646886,0.99\n1805,Wherever I May Roam,148,1,3,Ulrich,404323,13161169,0.99\n1806,Don't Tread On Me,148,1,3,Ulrich,240483,7827907,0.99\n1807,Through The Never,148,1,3,\"James Hetfield, Lars Ulrich and Kirk Hammett\",244375,8024047,0.99\n1808,Nothing Else Matters,148,1,3,Ulrich,388832,12606241,0.99\n1809,Of Wolf And Man,148,1,3,\"James Hetfield, Lars Ulrich and Kirk Hammett\",256835,8339785,0.99\n1810,The God That Failed,148,1,3,Ulrich,308610,10055959,0.99\n1811,My Friend Of Misery,148,1,3,\"James Hetfield, Lars Ulrich and Jason Newsted\",409547,13293515,0.99\n1812,The Struggle Within,148,1,3,Ulrich,234240,7654052,0.99\n1813,Helpless,149,1,3,Harris/Tatler,398315,12977902,0.99\n1814,The Small Hours,149,1,3,Holocaust,403435,13215133,0.99\n1815,The Wait,149,1,3,Killing Joke,295418,9688418,0.99\n1816,Crash Course In Brain Surgery,149,1,3,Bourge/Phillips/Shelley,190406,6233729,0.99\n1817,Last Caress/Green Hell,149,1,3,Danzig,209972,6854313,0.99\n1818,Am I Evil?,149,1,3,Harris/Tatler,470256,15387219,0.99\n1819,Blitzkrieg,149,1,3,Jones/Sirotto/Smith,216685,7090018,0.99\n1820,Breadfan,149,1,3,Bourge/Phillips/Shelley,341551,11100130,0.99\n1821,The Prince,149,1,3,Harris/Tatler,265769,8624492,0.99\n1822,Stone Cold Crazy,149,1,3,Deacon/May/Mercury/Taylor,137717,4514830,0.99\n1823,So What,149,1,3,Culmer/Exalt,189152,6162894,0.99\n1824,Killing Time,149,1,3,Sweet Savage,183693,6021197,0.99\n1825,Overkill,149,1,3,Clarke/Kilmister/Tayler,245133,7971330,0.99\n1826,Damage Case,149,1,3,Clarke/Farren/Kilmister/Tayler,220212,7212997,0.99\n1827,Stone Dead Forever,149,1,3,Clarke/Kilmister/Tayler,292127,9556060,0.99\n1828,Too Late Too Late,149,1,3,Clarke/Kilmister/Tayler,192052,6276291,0.99\n1829,Hit The Lights,150,1,3,\"James Hetfield, Lars Ulrich\",257541,8357088,0.99\n1830,The Four Horsemen,150,1,3,\"James Hetfield, Lars Ulrich, Dave Mustaine\",433188,14178138,0.99\n1831,Motorbreath,150,1,3,James Hetfield,188395,6153933,0.99\n1832,Jump In The Fire,150,1,3,\"James Hetfield, Lars Ulrich, Dave Mustaine\",281573,9135755,0.99\n1833,(Anesthesia) Pulling Teeth,150,1,3,Cliff Burton,254955,8234710,0.99\n1834,Whiplash,150,1,3,\"James Hetfield, Lars Ulrich\",249208,8102839,0.99\n1835,Phantom Lord,150,1,3,\"James Hetfield, Lars Ulrich, Dave Mustaine\",302053,9817143,0.99\n1836,No Remorse,150,1,3,\"James Hetfield, Lars Ulrich\",386795,12672166,0.99\n1837,Seek & Destroy,150,1,3,\"James Hetfield, Lars Ulrich\",415817,13452301,0.99\n1838,Metal Militia,150,1,3,\"James Hetfield, Lars Ulrich, Dave Mustaine\",311327,10141785,0.99\n1839,Ain't My Bitch,151,1,3,\"James Hetfield, Lars Ulrich\",304457,9931015,0.99\n1840,2 X 4,151,1,3,\"James Hetfield, Lars Ulrich, Kirk Hammett\",328254,10732251,0.99\n1841,The House Jack Built,151,1,3,\"James Hetfield, Lars Ulrich, Kirk Hammett\",398942,13005152,0.99\n1842,Until It Sleeps,151,1,3,\"James Hetfield, Lars Ulrich\",269740,8837394,0.99\n1843,King Nothing,151,1,3,\"James Hetfield, Lars Ulrich, Kirk Hammett\",328097,10681477,0.99\n1844,Hero Of The Day,151,1,3,\"James Hetfield, Lars Ulrich, Kirk Hammett\",261982,8540298,0.99\n1845,Bleeding Me,151,1,3,\"James Hetfield, Lars Ulrich, Kirk Hammett\",497998,16249420,0.99\n1846,Cure,151,1,3,\"James Hetfield, Lars Ulrich\",294347,9648615,0.99\n1847,Poor Twisted Me,151,1,3,\"James Hetfield, Lars Ulrich\",240065,7854349,0.99\n1848,Wasted My Hate,151,1,3,\"James Hetfield, Lars Ulrich, Kirk Hammett\",237296,7762300,0.99\n1849,Mama Said,151,1,3,\"James Hetfield, Lars Ulrich\",319764,10508310,0.99\n1850,Thorn Within,151,1,3,\"James Hetfield, Lars Ulrich, Kirk Hammett\",351738,11486686,0.99\n1851,Ronnie,151,1,3,\"James Hetfield, Lars Ulrich\",317204,10390947,0.99\n1852,The Outlaw Torn,151,1,3,\"James Hetfield, Lars Ulrich\",588721,19286261,0.99\n1853,Battery,152,1,3,J.Hetfield/L.Ulrich,312424,10229577,0.99\n1854,Master Of Puppets,152,1,3,K.Hammett,515239,16893720,0.99\n1855,The Thing That Should Not Be,152,1,3,K.Hammett,396199,12952368,0.99\n1856,Welcome Home (Sanitarium),152,1,3,K.Hammett,387186,12679965,0.99\n1857,Disposable Heroes,152,1,3,J.Hetfield/L.Ulrich,496718,16135560,0.99\n1858,Leper Messiah,152,1,3,C.Burton,347428,11310434,0.99\n1859,Orion,152,1,3,K.Hammett,500062,16378477,0.99\n1860,Damage Inc.,152,1,3,K.Hammett,330919,10725029,0.99\n1861,Fuel,153,1,3,\"Hetfield, Ulrich, Hammett\",269557,8876811,0.99\n1862,The Memory Remains,153,1,3,\"Hetfield, Ulrich\",279353,9110730,0.99\n1863,Devil's Dance,153,1,3,\"Hetfield, Ulrich\",318955,10414832,0.99\n1864,The Unforgiven II,153,1,3,\"Hetfield, Ulrich, Hammett\",395520,12886474,0.99\n1865,Better Than You,153,1,3,\"Hetfield, Ulrich\",322899,10549070,0.99\n1866,Slither,153,1,3,\"Hetfield, Ulrich, Hammett\",313103,10199789,0.99\n1867,Carpe Diem Baby,153,1,3,\"Hetfield, Ulrich, Hammett\",372480,12170693,0.99\n1868,Bad Seed,153,1,3,\"Hetfield, Ulrich, Hammett\",245394,8019586,0.99\n1869,Where The Wild Things Are,153,1,3,\"Hetfield, Ulrich, Newsted\",414380,13571280,0.99\n1870,Prince Charming,153,1,3,\"Hetfield, Ulrich\",365061,12009412,0.99\n1871,Low Man's Lyric,153,1,3,\"Hetfield, Ulrich\",457639,14855583,0.99\n1872,Attitude,153,1,3,\"Hetfield, Ulrich\",315898,10335734,0.99\n1873,Fixxxer,153,1,3,\"Hetfield, Ulrich, Hammett\",496065,16190041,0.99\n1874,Fight Fire With Fire,154,1,3,Metallica,285753,9420856,0.99\n1875,Ride The Lightning,154,1,3,Metallica,397740,13055884,0.99\n1876,For Whom The Bell Tolls,154,1,3,Metallica,311719,10159725,0.99\n1877,Fade To Black,154,1,3,Metallica,414824,13531954,0.99\n1878,Trapped Under Ice,154,1,3,Metallica,244532,7975942,0.99\n1879,Escape,154,1,3,Metallica,264359,8652332,0.99\n1880,Creeping Death,154,1,3,Metallica,396878,12955593,0.99\n1881,The Call Of Ktulu,154,1,3,Metallica,534883,17486240,0.99\n1882,Frantic,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,350458,11510849,0.99\n1883,St. Anger,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,441234,14363779,0.99\n1884,Some Kind Of Monster,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,505626,16557497,0.99\n1885,Dirty Window,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,324989,10670604,0.99\n1886,Invisible Kid,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,510197,16591800,0.99\n1887,My World,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,345626,11253756,0.99\n1888,Shoot Me Again,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,430210,14093551,0.99\n1889,Sweet Amber,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,327235,10616595,0.99\n1890,The Unnamed Feeling,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,429479,14014582,0.99\n1891,Purify,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,314017,10232537,0.99\n1892,All Within My Hands,155,1,3,Bob Rock/James Hetfield/Kirk Hammett/Lars Ulrich,527986,17162741,0.99\n1893,Blackened,156,1,3,\"James Hetfield, Lars Ulrich & Jason Newsted\",403382,13254874,0.99\n1894,...And Justice For All,156,1,3,\"James Hetfield, Lars Ulrich & Kirk Hammett\",585769,19262088,0.99\n1895,Eye Of The Beholder,156,1,3,\"James Hetfield, Lars Ulrich & Kirk Hammett\",385828,12747894,0.99\n1896,One,156,1,3,James Hetfield & Lars Ulrich,446484,14695721,0.99\n1897,The Shortest Straw,156,1,3,James Hetfield and Lars Ulrich,395389,13013990,0.99\n1898,Harvester Of Sorrow,156,1,3,James Hetfield and Lars Ulrich,345547,11377339,0.99\n1899,The Frayed Ends Of Sanity,156,1,3,\"James Hetfield, Lars Ulrich and Kirk Hammett\",464039,15198986,0.99\n1900,To Live Is To Die,156,1,3,\"James Hetfield, Lars Ulrich and Cliff Burton\",588564,19243795,0.99\n1901,Dyers Eve,156,1,3,\"James Hetfield, Lars Ulrich and Kirk Hammett\",313991,10302828,0.99\n1902,Springsville,157,1,2,J. Carisi,207725,6776219,0.99\n1903,The Maids Of Cadiz,157,1,2,L. Delibes,233534,7505275,0.99\n1904,The Duke,157,1,2,Dave Brubeck,214961,6977626,0.99\n1905,My Ship,157,1,2,\"Ira Gershwin, Kurt Weill\",268016,8581144,0.99\n1906,Miles Ahead,157,1,2,\"Miles Davis, Gil Evans\",209893,6807707,0.99\n1907,Blues For Pablo,157,1,2,Gil Evans,318328,10218398,0.99\n1908,New Rhumba,157,1,2,A. Jamal,276871,8980400,0.99\n1909,The Meaning Of The Blues,157,1,2,\"R. Troup, L. Worth\",168594,5395412,0.99\n1910,Lament,157,1,2,J.J. Johnson,134191,4293394,0.99\n1911,I Don't Wanna Be Kissed (By Anyone But You),157,1,2,\"H. Spina, J. Elliott\",191320,6219487,0.99\n1912,Springsville (Alternate Take),157,1,2,J. Carisi,196388,6382079,0.99\n1913,Blues For Pablo (Alternate Take),157,1,2,Gil Evans,212558,6900619,0.99\n1914,The Meaning Of The Blues/Lament (Alternate Take),157,1,2,\"J.J. Johnson/R. Troup, L. Worth\",309786,9912387,0.99\n1915,I Don't Wanna Be Kissed (By Anyone But You) (Alternate Take),157,1,2,\"H. Spina, J. Elliott\",192078,6254796,0.99\n1916,Coração De Estudante,158,1,7,\"Wagner Tiso, Milton Nascimento\",238550,7797308,0.99\n1917,A Noite Do Meu Bem,158,1,7,Dolores Duran,220081,7125225,0.99\n1918,Paisagem Na Janela,158,1,7,\"Lô Borges, Fernando Brant\",197694,6523547,0.99\n1919,Cuitelinho,158,1,7,Folclore,209397,6803970,0.99\n1920,Caxangá,158,1,7,\"Milton Nascimento, Fernando Brant\",245551,8144179,0.99\n1921,Nos Bailes Da Vida,158,1,7,\"Milton Nascimento, Fernando Brant\",275748,9126170,0.99\n1922,Menestrel Das Alagoas,158,1,7,\"Milton Nascimento, Fernando Brant\",199758,6542289,0.99\n1923,Brasil,158,1,7,\"Milton Nascimento, Fernando Brant\",155428,5252560,0.99\n1924,Canção Do Novo Mundo,158,1,7,\"Beto Guedes, Ronaldo Bastos\",215353,7032626,0.99\n1925,Um Gosto De Sol,158,1,7,\"Milton Nascimento, Ronaldo Bastos\",307200,9893875,0.99\n1926,Solar,158,1,7,\"Milton Nascimento, Fernando Brant\",156212,5098288,0.99\n1927,Para Lennon E McCartney,158,1,7,\"Lô Borges, Márcio Borges, Fernando Brant\",321828,10626920,0.99\n1928,\"Maria, Maria\",158,1,7,\"Milton Nascimento, Fernando Brant\",72463,2371543,0.99\n1929,Minas,159,1,7,\"Milton Nascimento, Caetano Veloso\",152293,4921056,0.99\n1930,\"Fé Cega, Faca Amolada\",159,1,7,\"Milton Nascimento, Ronaldo Bastos\",278099,9258649,0.99\n1931,Beijo Partido,159,1,7,Toninho Horta,229564,7506969,0.99\n1932,Saudade Dos Aviões Da Panair (Conversando No Bar),159,1,7,\"Milton Nascimento, Fernando Brant\",268721,8805088,0.99\n1933,Gran Circo,159,1,7,\"Milton Nascimento, Márcio Borges\",251297,8237026,0.99\n1934,Ponta de Areia,159,1,7,\"Milton Nascimento, Fernando Brant\",272796,8874285,0.99\n1935,Trastevere,159,1,7,\"Milton Nascimento, Ronaldo Bastos\",265665,8708399,0.99\n1936,Idolatrada,159,1,7,\"Milton Nascimento, Fernando Brant\",286249,9426153,0.99\n1937,Leila (Venha Ser Feliz),159,1,7,Milton Nascimento,209737,6898507,0.99\n1938,Paula E Bebeto,159,1,7,\"Milton Nascimento, Caetano Veloso\",135732,4583956,0.99\n1939,Simples,159,1,7,Nelson Angelo,133093,4326333,0.99\n1940,Norwegian Wood,159,1,7,\"John Lennon, Paul McCartney\",413910,13520382,0.99\n1941,Caso Você Queira Saber,159,1,7,\"Beto Guedes, Márcio Borges\",205688,6787901,0.99\n1942,Ace Of Spades,160,1,3,Clarke/Kilmister/Taylor,169926,5523552,0.99\n1943,Love Me Like A Reptile,160,1,3,Clarke/Kilmister/Taylor,203546,6616389,0.99\n1944,Shoot You In The Back,160,1,3,Clarke/Kilmister/Taylor,160026,5175327,0.99\n1945,Live To Win,160,1,3,Clarke/Kilmister/Taylor,217626,7102182,0.99\n1946,Fast And Loose,160,1,3,Clarke/Kilmister/Taylor,203337,6643350,0.99\n1947,(We Are) The Road Crew,160,1,3,Clarke/Kilmister/Taylor,192600,6283035,0.99\n1948,Fire Fire,160,1,3,Clarke/Kilmister/Taylor,164675,5416114,0.99\n1949,Jailbait,160,1,3,Clarke/Kilmister/Taylor,213916,6983609,0.99\n1950,Dance,160,1,3,Clarke/Kilmister/Taylor,158432,5155099,0.99\n1951,Bite The Bullet,160,1,3,Clarke/Kilmister/Taylor,98115,3195536,0.99\n1952,The Chase Is Better Than The Catch,160,1,3,Clarke/Kilmister/Taylor,258403,8393310,0.99\n1953,The Hammer,160,1,3,Clarke/Kilmister/Taylor,168071,5543267,0.99\n1954,Dirty Love,160,1,3,Clarke/Kilmister/Taylor,176457,5805241,0.99\n1955,Please Don't Touch,160,1,3,Heath/Robinson,169926,5557002,0.99\n1956,Emergency,160,1,3,Dufort/Johnson/McAuliffe/Williams,180427,5828728,0.99\n1957,Kir Royal,161,1,16,Mônica Marianno,234788,7706552,0.99\n1958,O Que Vai Em Meu Coração,161,1,16,Mônica Marianno,255373,8366846,0.99\n1959,Aos Leões,161,1,16,Mônica Marianno,234684,7790574,0.99\n1960,Dois Índios,161,1,16,Mônica Marianno,219271,7213072,0.99\n1961,Noite Negra,161,1,16,Mônica Marianno,206811,6819584,0.99\n1962,Beijo do Olhar,161,1,16,Mônica Marianno,252682,8369029,0.99\n1963,É Fogo,161,1,16,Mônica Marianno,194873,6501520,0.99\n1964,Já Foi,161,1,16,Mônica Marianno,245681,8094872,0.99\n1965,Só Se For Pelo Cabelo,161,1,16,Mônica Marianno,238288,8006345,0.99\n1966,No Clima,161,1,16,Mônica Marianno,249495,8362040,0.99\n1967,A Moça e a Chuva,161,1,16,Mônica Marianno,274625,8929357,0.99\n1968,Demorou!,161,1,16,Mônica Marianno,39131,1287083,0.99\n1969,Bitter Pill,162,1,3,Mick Mars/Nikki Sixx/Tommy Lee/Vince Neil,266814,8666786,0.99\n1970,Enslaved,162,1,3,Mick Mars/Nikki Sixx/Tommy Lee,269844,8789966,0.99\n1971,\"Girls, Girls, Girls\",162,1,3,Mick Mars/Nikki Sixx/Tommy Lee,270288,8874814,0.99\n1972,Kickstart My Heart,162,1,3,Nikki Sixx,283559,9237736,0.99\n1973,Wild Side,162,1,3,Nikki Sixx/Tommy Lee/Vince Neil,276767,9116997,0.99\n1974,Glitter,162,1,3,Bryan Adams/Nikki Sixx/Scott Humphrey,340114,11184094,0.99\n1975,Dr. Feelgood,162,1,3,Mick Mars/Nikki Sixx,282618,9281875,0.99\n1976,Same Ol' Situation,162,1,3,Mick Mars/Nikki Sixx/Tommy Lee/Vince Neil,254511,8283958,0.99\n1977,Home Sweet Home,162,1,3,Nikki Sixx/Tommy Lee/Vince Neil,236904,7697538,0.99\n1978,Afraid,162,1,3,Nikki Sixx,248006,8077464,0.99\n1979,Don't Go Away Mad (Just Go Away),162,1,3,Mick Mars/Nikki Sixx,279980,9188156,0.99\n1980,Without You,162,1,3,Mick Mars/Nikki Sixx,268956,8738371,0.99\n1981,Smokin' in The Boys Room,162,1,3,Cub Coda/Michael Lutz,206837,6735408,0.99\n1982,Primal Scream,162,1,3,Mick Mars/Nikki Sixx/Tommy Lee/Vince Neil,286197,9421164,0.99\n1983,Too Fast For Love,162,1,3,Nikki Sixx,200829,6580542,0.99\n1984,Looks That Kill,162,1,3,Nikki Sixx,240979,7831122,0.99\n1985,Shout At The Devil,162,1,3,Nikki Sixx,221962,7281974,0.99\n1986,Intro,163,1,1,Kurt Cobain,52218,1688527,0.99\n1987,School,163,1,1,Kurt Cobain,160235,5234885,0.99\n1988,Drain You,163,1,1,Kurt Cobain,215196,7013175,0.99\n1989,Aneurysm,163,1,1,Nirvana,271516,8862545,0.99\n1990,Smells Like Teen Spirit,163,1,1,Nirvana,287190,9425215,0.99\n1991,Been A Son,163,1,1,Kurt Cobain,127555,4170369,0.99\n1992,Lithium,163,1,1,Kurt Cobain,250017,8148800,0.99\n1993,Sliver,163,1,1,Kurt Cobain,116218,3784567,0.99\n1994,Spank Thru,163,1,1,Kurt Cobain,190354,6186487,0.99\n1995,Scentless Apprentice,163,1,1,Nirvana,211200,6898177,0.99\n1996,Heart-Shaped Box,163,1,1,Kurt Cobain,281887,9210982,0.99\n1997,Milk It,163,1,1,Kurt Cobain,225724,7406945,0.99\n1998,Negative Creep,163,1,1,Kurt Cobain,163761,5354854,0.99\n1999,Polly,163,1,1,Kurt Cobain,149995,4885331,0.99\n2000,Breed,163,1,1,Kurt Cobain,208378,6759080,0.99\n2001,Tourette's,163,1,1,Kurt Cobain,115591,3753246,0.99\n2002,Blew,163,1,1,Kurt Cobain,216346,7096936,0.99\n2003,Smells Like Teen Spirit,164,1,1,Kurt Cobain,301296,9823847,0.99\n2004,In Bloom,164,1,1,Kurt Cobain,254928,8327077,0.99\n2005,Come As You Are,164,1,1,Kurt Cobain,219219,7123357,0.99\n2006,Breed,164,1,1,Kurt Cobain,183928,5984812,0.99\n2007,Lithium,164,1,1,Kurt Cobain,256992,8404745,0.99\n2008,Polly,164,1,1,Kurt Cobain,177031,5788407,0.99\n2009,Territorial Pissings,164,1,1,Kurt Cobain,143281,4613880,0.99\n2010,Drain You,164,1,1,Kurt Cobain,223973,7273440,0.99\n2011,Lounge Act,164,1,1,Kurt Cobain,156786,5093635,0.99\n2012,Stay Away,164,1,1,Kurt Cobain,212636,6956404,0.99\n2013,On A Plain,164,1,1,Kurt Cobain,196440,6390635,0.99\n2014,Something In The Way,164,1,1,Kurt Cobain,230556,7472168,0.99\n2015,Time,165,1,1,,96888,3124455,0.99\n2016,P.S.Apareça,165,1,1,,209188,6842244,0.99\n2017,Sangue Latino,165,1,1,,223033,7354184,0.99\n2018,Folhas Secas,165,1,1,,161253,5284522,0.99\n2019,Poeira,165,1,1,,267075,8784141,0.99\n2020,Mágica,165,1,1,,233743,7627348,0.99\n2021,Quem Mata A Mulher Mata O Melhor,165,1,1,,262791,8640121,0.99\n2022,Mundaréu,165,1,1,,217521,7158975,0.99\n2023,O Braço Da Minha Guitarra,165,1,1,,258351,8469531,0.99\n2024,Deus,165,1,1,,284160,9188110,0.99\n2025,Mãe Terra,165,1,1,,306625,9949269,0.99\n2026,Às Vezes,165,1,1,,330292,10706614,0.99\n2027,Menino De Rua,165,1,1,,329795,10784595,0.99\n2028,Prazer E Fé,165,1,1,,214831,7031383,0.99\n2029,Elza,165,1,1,,199105,6517629,0.99\n2030,Requebra,166,1,7,,240744,8010811,0.99\n2031,Nossa Gente (Avisa Là),166,1,7,,188212,6233201,0.99\n2032,Olodum - Alegria Geral,166,1,7,,233404,7754245,0.99\n2033,Madagáscar Olodum,166,1,7,,252264,8270584,0.99\n2034,Faraó Divindade Do Egito,166,1,7,,228571,7523278,0.99\n2035,Todo Amor (Asas Da Liberdade),166,1,7,,245133,8121434,0.99\n2036,Denúncia,166,1,7,,159555,5327433,0.99\n2037,\"Olodum, A Banda Do Pelô\",166,1,7,,146599,4900121,0.99\n2038,Cartao Postal,166,1,7,,211565,7082301,0.99\n2039,Jeito Faceiro,166,1,7,,217286,7233608,0.99\n2040,Revolta Olodum,166,1,7,,230191,7557065,0.99\n2041,Reggae Odoyá,166,1,7,,224470,7499807,0.99\n2042,Protesto Do Olodum (Ao Vivo),166,1,7,,206001,6766104,0.99\n2043,Olodum - Smile (Instrumental),166,1,7,,235833,7871409,0.99\n2044,Vulcão Dub - Fui Eu,167,1,7,Bi Ribeira/Herbert Vianna/João Barone,287059,9495202,0.99\n2045,O Trem Da Juventude,167,1,7,Herbert Vianna,225880,7507655,0.99\n2046,Manguetown,167,1,7,Chico Science/Dengue/Lúcio Maia,162925,5382018,0.99\n2047,\"Um Amor, Um Lugar\",167,1,7,Herbert Vianna,184555,6090334,0.99\n2048,Bora-Bora,167,1,7,Herbert Vianna,182987,6036046,0.99\n2049,Vai Valer,167,1,7,Herbert Vianna,206524,6899778,0.99\n2050,I Feel Good (I Got You) - Sossego,167,1,7,James Brown/Tim Maia,244976,8091302,0.99\n2051,Uns Dias,167,1,7,Herbert Vianna,240796,7931552,0.99\n2052,Sincero Breu,167,1,7,C. A./C.A./Celso Alvim/Herbert Vianna/Mário Moura/Pedro Luís/Sidon Silva,208013,6921669,0.99\n2053,Meu Erro,167,1,7,Herbert Vianna,188577,6192791,0.99\n2054,Selvagem,167,1,7,Bi Ribeiro/Herbert Vianna/João Barone,148558,4942831,0.99\n2055,Brasília 5:31,167,1,7,Herbert Vianna,178337,5857116,0.99\n2056,Tendo A Lua,167,1,7,Herbert Vianna/Tet Tillett,198922,6568180,0.99\n2057,Que País É Este,167,1,7,Renato Russo,216685,7137865,0.99\n2058,Navegar Impreciso,167,1,7,Herbert Vianna,262870,8761283,0.99\n2059,Feira Moderna,167,1,7,Beto Guedes/Fernando Brant/L Borges,182517,6001793,0.99\n2060,Tequila - Lourinha Bombril (Parate Y Mira),167,1,7,Bahiano/Chuck Rio/Diego Blanco/Herbert Vianna,255738,8514961,0.99\n2061,Vamo Batê Lata,167,1,7,Herbert Vianna,228754,7585707,0.99\n2062,Life During Wartime,167,1,7,Chris Frantz/David Byrne/Jerry Harrison/Tina Weymouth,259186,8543439,0.99\n2063,Nebulosa Do Amor,167,1,7,Herbert Vianna,203415,6732496,0.99\n2064,Caleidoscópio,167,1,7,Herbert Vianna,256522,8484597,0.99\n2065,Trac Trac,168,1,7,Fito Paez/Herbert Vianna,231653,7638256,0.99\n2066,Tendo A Lua,168,1,7,Herbert Vianna/Tetê Tillet,219585,7342776,0.99\n2067,Mensagen De Amor (2000),168,1,7,Herbert Vianna,183588,6061324,0.99\n2068,Lourinha Bombril,168,1,7,Bahiano/Diego Blanco/Herbert Vianna,159895,5301882,0.99\n2069,La Bella Luna,168,1,7,Herbert Vianna,192653,6428598,0.99\n2070,Busca Vida,168,1,7,Herbert Vianna,176431,5798663,0.99\n2071,Uma Brasileira,168,1,7,Carlinhos Brown/Herbert Vianna,217573,7280574,0.99\n2072,Luis Inacio (300 Picaretas),168,1,7,Herbert Vianna,198191,6576790,0.99\n2073,Saber Amar,168,1,7,Herbert Vianna,202788,6723733,0.99\n2074,Ela Disse Adeus,168,1,7,Herbert Vianna,226298,7608999,0.99\n2075,O Amor Nao Sabe Esperar,168,1,7,Herbert Vianna,241084,8042534,0.99\n2076,Aonde Quer Que Eu Va,168,1,7,Herbert Vianna/Paulo Sérgio Valle,258089,8470121,0.99\n2077,Caleidoscópio,169,1,7,,211330,7000017,0.99\n2078,Óculos,169,1,7,,219271,7262419,0.99\n2079,Cinema Mudo,169,1,7,,227918,7612168,0.99\n2080,Alagados,169,1,7,,302393,10255463,0.99\n2081,Lanterna Dos Afogados,169,1,7,,190197,6264318,0.99\n2082,Melô Do Marinheiro,169,1,7,,208352,6905668,0.99\n2083,Vital E Sua Moto,169,1,7,,210207,6902878,0.99\n2084,O Beco,169,1,7,,189178,6293184,0.99\n2085,Meu Erro,169,1,7,,208431,6893533,0.99\n2086,Perplexo,169,1,7,,161175,5355013,0.99\n2087,Me Liga,169,1,7,,229590,7565912,0.99\n2088,Quase Um Segundo,169,1,7,,275644,8971355,0.99\n2089,Selvagem,169,1,7,,245890,8141084,0.99\n2090,Romance Ideal,169,1,7,,250070,8260477,0.99\n2091,Será Que Vai Chover?,169,1,7,,337057,11133830,0.99\n2092,SKA,169,1,7,,148871,4943540,0.99\n2093,Bark at the Moon,170,2,1,O. Osbourne,257252,4601224,0.99\n2094,I Don't Know,171,2,1,\"B. Daisley, O. Osbourne & R. Rhoads\",312980,5525339,0.99\n2095,Crazy Train,171,2,1,\"B. Daisley, O. Osbourne & R. Rhoads\",295960,5255083,0.99\n2096,Flying High Again,172,2,1,\"L. Kerslake, O. Osbourne, R. Daisley & R. Rhoads\",290851,5179599,0.99\n2097,\"Mama, I'm Coming Home\",173,2,1,\"L. Kilmister, O. Osbourne & Z. Wylde\",251586,4302390,0.99\n2098,No More Tears,173,2,1,\"J. Purdell, M. Inez, O. Osbourne, R. Castillo & Z. Wylde\",444358,7362964,0.99\n2099,I Don't Know,174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads\",283088,9207869,0.99\n2100,Crazy Train,174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads\",322716,10517408,0.99\n2101,Believer,174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads\",308897,10003794,0.99\n2102,Mr. Crowley,174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads\",344241,11184130,0.99\n2103,Flying High Again,174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads, L. Kerslake\",261224,8481822,0.99\n2104,Relvelation (Mother Earth),174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads\",349440,11367866,0.99\n2105,Steal Away (The Night),174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads\",485720,15945806,0.99\n2106,Suicide Solution (With Guitar Solo),174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads\",467069,15119938,0.99\n2107,Iron Man,174,1,3,\"A. F. Iommi, W. Ward, T. Butler, J. Osbourne\",172120,5609799,0.99\n2108,Children Of The Grave,174,1,3,\"A. F. Iommi, W. Ward, T. Butler, J. Osbourne\",357067,11626740,0.99\n2109,Paranoid,174,1,3,\"A. F. Iommi, W. Ward, T. Butler, J. Osbourne\",176352,5729813,0.99\n2110,Goodbye To Romance,174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads\",334393,10841337,0.99\n2111,No Bone Movies,174,1,3,\"O. Osbourne, R. Daisley, R. Rhoads\",249208,8095199,0.99\n2112,Dee,174,1,3,R. Rhoads,261302,8555963,0.99\n2113,Shining In The Light,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",240796,7951688,0.99\n2114,When The World Was Young,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",373394,12198930,0.99\n2115,Upon A Golden Horse,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",232359,7594829,0.99\n2116,Blue Train,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",405028,13170391,0.99\n2117,Please Read The Letter,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",262112,8603372,0.99\n2118,Most High,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",336535,10999203,0.99\n2119,Heart In Your Hand,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",230896,7598019,0.99\n2120,Walking Into Clarksdale,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",318511,10396315,0.99\n2121,Burning Up,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",321619,10525136,0.99\n2122,When I Was A Child,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",345626,11249456,0.99\n2123,House Of Love,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",335699,10990880,0.99\n2124,Sons Of Freedom,175,1,1,\"Jimmy Page, Robert Plant, Charlie Jones, Michael Lee\",246465,8087944,0.99\n2125,United Colours,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",330266,10939131,0.99\n2126,Slug,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",281469,9295950,0.99\n2127,Your Blue Room,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",328228,10867860,0.99\n2128,Always Forever Now,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",383764,12727928,0.99\n2129,A Different Kind Of Blue,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",120816,3884133,0.99\n2130,Beach Sequence,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",212297,6928259,0.99\n2131,Miss Sarajevo,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",340767,11064884,0.99\n2132,Ito Okashi,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",205087,6572813,0.99\n2133,One Minute Warning,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",279693,9335453,0.99\n2134,Corpse (These Chains Are Way Too Long),176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",214909,6920451,0.99\n2135,Elvis Ate America,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",180166,5851053,0.99\n2136,Plot 180,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",221596,7253729,0.99\n2137,Theme From The Swan,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",203911,6638076,0.99\n2138,Theme From Let's Go Native,176,1,10,\"Brian Eno, Bono, Adam Clayton, The Edge & Larry Mullen Jnr.\",186723,6179777,0.99\n2139,Wrathchild,177,1,1,Steve Harris,170396,5499390,0.99\n2140,Killers,177,1,1,Paul Di'Anno/Steve Harris,309995,10009697,0.99\n2141,Prowler,177,1,1,Steve Harris,240274,7782963,0.99\n2142,Murders In The Rue Morgue,177,1,1,Steve Harris,258638,8360999,0.99\n2143,Women In Uniform,177,1,1,Greg Macainsh,189936,6139651,0.99\n2144,Remember Tomorrow,177,1,1,Paul Di'Anno/Steve Harris,326426,10577976,0.99\n2145,Sanctuary,177,1,1,David Murray/Paul Di'Anno/Steve Harris,198844,6423543,0.99\n2146,Running Free,177,1,1,Paul Di'Anno/Steve Harris,199706,6483496,0.99\n2147,Phantom Of The Opera,177,1,1,Steve Harris,418168,13585530,0.99\n2148,Iron Maiden,177,1,1,Steve Harris,235232,7600077,0.99\n2149,Corduroy,178,1,1,Pearl Jam & Eddie Vedder,305293,9991106,0.99\n2150,Given To Fly,178,1,1,Eddie Vedder & Mike McCready,233613,7678347,0.99\n2151,\"Hail, Hail\",178,1,1,Stone Gossard & Eddie Vedder & Jeff Ament & Mike McCready,223764,7364206,0.99\n2152,Daughter,178,1,1,Dave Abbruzzese & Jeff Ament & Stone Gossard & Mike McCready & Eddie Vedder,407484,13420697,0.99\n2153,Elderly Woman Behind The Counter In A Small Town,178,1,1,Dave Abbruzzese & Jeff Ament & Stone Gossard & Mike McCready & Eddie Vedder,229328,7509304,0.99\n2154,Untitled,178,1,1,Pearl Jam,122801,3957141,0.99\n2155,MFC,178,1,1,Eddie Vedder,148192,4817665,0.99\n2156,Go,178,1,1,Dave Abbruzzese & Jeff Ament & Stone Gossard & Mike McCready & Eddie Vedder,161541,5290810,0.99\n2157,Red Mosquito,178,1,1,Jeff Ament & Stone Gossard & Jack Irons & Mike McCready & Eddie Vedder,242991,7944923,0.99\n2158,Even Flow,178,1,1,Stone Gossard & Eddie Vedder,317100,10394239,0.99\n2159,Off He Goes,178,1,1,Eddie Vedder,343222,11245109,0.99\n2160,Nothingman,178,1,1,Jeff Ament & Eddie Vedder,278595,9107017,0.99\n2161,Do The Evolution,178,1,1,Eddie Vedder & Stone Gossard,225462,7377286,0.99\n2162,Better Man,178,1,1,Eddie Vedder,246204,8019563,0.99\n2163,Black,178,1,1,Stone Gossard & Eddie Vedder,415712,13580009,0.99\n2164,F*Ckin' Up,178,1,1,Neil Young,377652,12360893,0.99\n2165,Life Wasted,179,1,4,Stone Gossard,234344,7610169,0.99\n2166,World Wide Suicide,179,1,4,Eddie Vedder,209188,6885908,0.99\n2167,Comatose,179,1,4,Mike McCready & Stone Gossard,139990,4574516,0.99\n2168,Severed Hand,179,1,4,Eddie Vedder,270341,8817438,0.99\n2169,Marker In The Sand,179,1,4,Mike McCready,263235,8656578,0.99\n2170,Parachutes,179,1,4,Stone Gossard,216555,7074973,0.99\n2171,Unemployable,179,1,4,Matt Cameron & Mike McCready,184398,6066542,0.99\n2172,Big Wave,179,1,4,Jeff Ament,178573,5858788,0.99\n2173,Gone,179,1,4,Eddie Vedder,249547,8158204,0.99\n2174,Wasted Reprise,179,1,4,Stone Gossard,53733,1731020,0.99\n2175,Army Reserve,179,1,4,Jeff Ament,225567,7393771,0.99\n2176,Come Back,179,1,4,Eddie Vedder & Mike McCready,329743,10768701,0.99\n2177,Inside Job,179,1,4,Eddie Vedder & Mike McCready,428643,14006924,0.99\n2178,Can't Keep,180,1,1,Eddie Vedder,219428,7215713,0.99\n2179,Save You,180,1,1,Eddie Vedder/Jeff Ament/Matt Cameron/Mike McCready/Stone Gossard,230112,7609110,0.99\n2180,Love Boat Captain,180,1,1,Eddie Vedder,276453,9016789,0.99\n2181,Cropduster,180,1,1,Matt Cameron,231888,7588928,0.99\n2182,Ghost,180,1,1,Jeff Ament,195108,6383772,0.99\n2183,I Am Mine,180,1,1,Eddie Vedder,215719,7086901,0.99\n2184,Thumbing My Way,180,1,1,Eddie Vedder,250226,8201437,0.99\n2185,You Are,180,1,1,Matt Cameron,270863,8938409,0.99\n2186,Get Right,180,1,1,Matt Cameron,158589,5223345,0.99\n2187,Green Disease,180,1,1,Eddie Vedder,161253,5375818,0.99\n2188,Help Help,180,1,1,Jeff Ament,215092,7033002,0.99\n2189,Bushleager,180,1,1,Stone Gossard,237479,7849757,0.99\n2190,1/2 Full,180,1,1,Jeff Ament,251010,8197219,0.99\n2191,Arc,180,1,1,Pearl Jam,65593,2099421,0.99\n2192,All or None,180,1,1,Stone Gossard,277655,9104728,0.99\n2193,Once,181,1,1,Stone Gossard,231758,7561555,0.99\n2194,Evenflow,181,1,1,Stone Gossard,293720,9622017,0.99\n2195,Alive,181,1,1,Stone Gossard,341080,11176623,0.99\n2196,Why Go,181,1,1,Jeff Ament,200254,6539287,0.99\n2197,Black,181,1,1,Dave Krusen/Stone Gossard,343823,11213314,0.99\n2198,Jeremy,181,1,1,Jeff Ament,318981,10447222,0.99\n2199,Oceans,181,1,1,Jeff Ament/Stone Gossard,162194,5282368,0.99\n2200,Porch,181,1,1,Eddie Vedder,210520,6877475,0.99\n2201,Garden,181,1,1,Jeff Ament/Stone Gossard,299154,9740738,0.99\n2202,Deep,181,1,1,Jeff Ament/Stone Gossard,258324,8432497,0.99\n2203,Release,181,1,1,Jeff Ament/Mike McCready/Stone Gossard,546063,17802673,0.99\n2204,Go,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,193123,6351920,0.99\n2205,Animal,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,169325,5503459,0.99\n2206,Daughter,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,235598,7824586,0.99\n2207,Glorified G,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,206968,6772116,0.99\n2208,Dissident,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,215510,7034500,0.99\n2209,W.M.A.,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,359262,12037261,0.99\n2210,Blood,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,170631,5551478,0.99\n2211,Rearviewmirror,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,284186,9321053,0.99\n2212,Rats,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,255425,8341934,0.99\n2213,Elderly Woman Behind The Counter In A Small Town,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,196336,6499398,0.99\n2214,Leash,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,189257,6191560,0.99\n2215,Indifference,182,1,1,Dave Abbruzzese/Eddie Vedder/Jeff Ament/Mike McCready/Stone Gossard,302053,9756133,0.99\n2216,Johnny B. Goode,141,1,8,,243200,8092024,0.99\n2217,Don't Look Back,141,1,8,,221100,7344023,0.99\n2218,Jah Seh No,141,1,8,,276871,9134476,0.99\n2219,I'm The Toughest,141,1,8,,230191,7657594,0.99\n2220,Nothing But Love,141,1,8,,221570,7335228,0.99\n2221,Buk-In-Hamm Palace,141,1,8,,265665,8964369,0.99\n2222,Bush Doctor,141,1,8,,239751,7942299,0.99\n2223,Wanted Dread And Alive,141,1,8,,260310,8670933,0.99\n2224,Mystic Man,141,1,8,,353671,11812170,0.99\n2225,Coming In Hot,141,1,8,,213054,7109414,0.99\n2226,Pick Myself Up,141,1,8,,234684,7788255,0.99\n2227,Crystal Ball,141,1,8,,309733,10319296,0.99\n2228,Equal Rights Downpresser Man,141,1,8,,366733,12086524,0.99\n2229,Speak To Me/Breathe,183,1,1,\"Mason/Waters, Gilmour, Wright\",234213,7631305,0.99\n2230,On The Run,183,1,1,\"Gilmour, Waters\",214595,7206300,0.99\n2231,Time,183,1,1,\"Mason, Waters, Wright, Gilmour\",425195,13955426,0.99\n2232,The Great Gig In The Sky,183,1,1,\"Wright, Waters\",284055,9147563,0.99\n2233,Money,183,1,1,Waters,391888,12930070,0.99\n2234,Us And Them,183,1,1,\"Waters, Wright\",461035,15000299,0.99\n2235,Any Colour You Like,183,1,1,\"Gilmour, Mason, Wright, Waters\",205740,6707989,0.99\n2236,Brain Damage,183,1,1,Waters,230556,7497655,0.99\n2237,Eclipse,183,1,1,Waters,125361,4065299,0.99\n2238,ZeroVinteUm,184,1,17,,315637,10426550,0.99\n2239,Queimando Tudo,184,1,17,,172591,5723677,0.99\n2240,Hip Hop Rio,184,1,17,,151536,4991935,0.99\n2241,Bossa,184,1,17,,29048,967098,0.99\n2242,100% HardCore,184,1,17,,165146,5407744,0.99\n2243,Biruta,184,1,17,,213263,7108200,0.99\n2244,Mão Na Cabeça,184,1,17,,202631,6642753,0.99\n2245,O Bicho Tá Pregando,184,1,17,,171964,5683369,0.99\n2246,Adoled (Ocean),184,1,17,,185103,6009946,0.99\n2247,Seus Amigos,184,1,17,,100858,3304738,0.99\n2248,Paga Pau,184,1,17,,197485,6529041,0.99\n2249,Rappers Reais,184,1,17,,202004,6684160,0.99\n2250,Nega Do Cabelo Duro,184,1,17,,121808,4116536,0.99\n2251,Hemp Family,184,1,17,,205923,6806900,0.99\n2252,Quem Me Cobrou?,184,1,17,,121704,3947664,0.99\n2253,Se Liga,184,1,17,,410409,13559173,0.99\n2254,Bohemian Rhapsody,185,1,1,\"Mercury, Freddie\",358948,11619868,0.99\n2255,Another One Bites The Dust,185,1,1,\"Deacon, John\",216946,7172355,0.99\n2256,Killer Queen,185,1,1,\"Mercury, Freddie\",182099,5967749,0.99\n2257,Fat Bottomed Girls,185,1,1,\"May, Brian\",204695,6630041,0.99\n2258,Bicycle Race,185,1,1,\"Mercury, Freddie\",183823,6012409,0.99\n2259,You're My Best Friend,185,1,1,\"Deacon, John\",172225,5602173,0.99\n2260,Don't Stop Me Now,185,1,1,\"Mercury, Freddie\",211826,6896666,0.99\n2261,Save Me,185,1,1,\"May, Brian\",228832,7444624,0.99\n2262,Crazy Little Thing Called Love,185,1,1,\"Mercury, Freddie\",164231,5435501,0.99\n2263,Somebody To Love,185,1,1,\"Mercury, Freddie\",297351,9650520,0.99\n2264,Now I'm Here,185,1,1,\"May, Brian\",255346,8328312,0.99\n2265,Good Old-Fashioned Lover Boy,185,1,1,\"Mercury, Freddie\",175960,5747506,0.99\n2266,Play The Game,185,1,1,\"Mercury, Freddie\",213368,6915832,0.99\n2267,Flash,185,1,1,\"May, Brian\",168489,5464986,0.99\n2268,Seven Seas Of Rhye,185,1,1,\"Mercury, Freddie\",170553,5539957,0.99\n2269,We Will Rock You,185,1,1,\"Deacon, John/May, Brian\",122880,4026955,0.99\n2270,We Are The Champions,185,1,1,\"Mercury, Freddie\",180950,5880231,0.99\n2271,We Will Rock You,186,1,1,May,122671,4026815,0.99\n2272,We Are The Champions,186,1,1,Mercury,182883,5939794,0.99\n2273,Sheer Heart Attack,186,1,1,Taylor,207386,6642685,0.99\n2274,\"All Dead, All Dead\",186,1,1,May,190119,6144878,0.99\n2275,Spread Your Wings,186,1,1,Deacon,275356,8936992,0.99\n2276,Fight From The Inside,186,1,1,Taylor,184737,6078001,0.99\n2277,\"Get Down, Make Love\",186,1,1,Mercury,231235,7509333,0.99\n2278,Sleep On The Sidewalk,186,1,1,May,187428,6099840,0.99\n2279,Who Needs You,186,1,1,Deacon,186958,6292969,0.99\n2280,It's Late,186,1,1,May,386194,12519388,0.99\n2281,My Melancholy Blues,186,1,1,Mercury,206471,6691838,0.99\n2282,Shiny Happy People,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,226298,7475323,0.99\n2283,Me In Honey,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,246674,8194751,0.99\n2284,Radio Song,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,255477,8421172,0.99\n2285,Pop Song 89,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,185730,6132218,0.99\n2286,Get Up,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,160235,5264376,0.99\n2287,You Are The Everything,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,226298,7373181,0.99\n2288,Stand,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,192862,6349090,0.99\n2289,World Leader Pretend,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,259761,8537282,0.99\n2290,The Wrong Child,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,216633,7065060,0.99\n2291,Orange Crush,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,231706,7742894,0.99\n2292,Turn You Inside-Out,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,257358,8395671,0.99\n2293,Hairshirt,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,235911,7753807,0.99\n2294,I Remember California,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,304013,9950311,0.99\n2295,Untitled,188,1,4,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,191503,6332426,0.99\n2296,How The West Was Won And Where It Got Us,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,271151,8994291,0.99\n2297,The Wake-Up Bomb,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,308532,10077337,0.99\n2298,New Test Leper,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,326791,10866447,0.99\n2299,Undertow,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,309498,10131005,0.99\n2300,E-Bow The Letter,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,324963,10714576,0.99\n2301,Leave,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,437968,14433365,0.99\n2302,Departure,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,209423,6818425,0.99\n2303,Bittersweet Me,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,245812,8114718,0.99\n2304,Be Mine,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,333087,10790541,0.99\n2305,Binky The Doormat,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,301688,9950320,0.99\n2306,Zither,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,154148,5032962,0.99\n2307,\"So Fast, So Numb\",189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,252682,8341223,0.99\n2308,Low Desert,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,212062,6989288,0.99\n2309,Electrolite,189,1,1,Bill Berry-Peter Buck-Mike Mills-Michael Stipe,245315,8051199,0.99\n2310,Losing My Religion,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,269035,8885672,0.99\n2311,Low,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,296777,9633860,0.99\n2312,Near Wild Heaven,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,199862,6610009,0.99\n2313,Endgame,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,230687,7664479,0.99\n2314,Belong,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,247013,8219375,0.99\n2315,Half A World Away,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,208431,6837283,0.99\n2316,Texarkana,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,220081,7260681,0.99\n2317,Country Feedback,187,1,4,Bill Berry/Michael Stipe/Mike Mills/Peter Buck,249782,8178943,0.99\n2318,Carnival Of Sorts,190,1,4,R.E.M.,233482,7669658,0.99\n2319,Radio Free Aurope,190,1,4,R.E.M.,245315,8163490,0.99\n2320,Perfect Circle,190,1,4,R.E.M.,208509,6898067,0.99\n2321,Talk About The Passion,190,1,4,R.E.M.,203206,6725435,0.99\n2322,So Central Rain,190,1,4,R.E.M.,194768,6414550,0.99\n2323,Don't Go Back To Rockville,190,1,4,R.E.M.,272352,9010715,0.99\n2324,Pretty Persuasion,190,1,4,R.E.M.,229929,7577754,0.99\n2325,Green Grow The Rushes,190,1,4,R.E.M.,225671,7422425,0.99\n2326,Can't Get There From Here,190,1,4,R.E.M.,220630,7285936,0.99\n2327,Driver 8,190,1,4,R.E.M.,204747,6779076,0.99\n2328,Fall On Me,190,1,4,R.E.M.,172016,5676811,0.99\n2329,I Believe,190,1,4,R.E.M.,227709,7542929,0.99\n2330,Cuyahoga,190,1,4,R.E.M.,260623,8591057,0.99\n2331,The One I Love,190,1,4,R.E.M.,197355,6495125,0.99\n2332,The Finest Worksong,190,1,4,R.E.M.,229276,7574856,0.99\n2333,It's The End Of The World As We Know It (And I Feel Fine),190,1,4,R.E.M.,244819,7998987,0.99\n2334,Infeliz Natal,191,1,4,Rodolfo,138266,4503299,0.99\n2335,A Sua,191,1,4,Rodolfo,142132,4622064,0.99\n2336,Papeau Nuky Doe,191,1,4,Rodolfo,121652,3995022,0.99\n2337,Merry Christmas,191,1,4,Rodolfo,126040,4166652,0.99\n2338,Bodies,191,1,4,Rodolfo,180035,5873778,0.99\n2339,Puteiro Em João Pessoa,191,1,4,Rodolfo,195578,6395490,0.99\n2340,Esporrei Na Manivela,191,1,4,Rodolfo,293276,9618499,0.99\n2341,Bê-a-Bá,191,1,4,Rodolfo,249051,8130636,0.99\n2342,Cajueiro,191,1,4,Rodolfo,158589,5164837,0.99\n2343,Palhas Do Coqueiro,191,1,4,Rodolfo,133851,4396466,0.99\n2344,Maluco Beleza,192,1,1,,203206,6628067,0.99\n2345,O Dia Em Que A Terra Parou,192,1,1,,261720,8586678,0.99\n2346,No Fundo Do Quintal Da Escola,192,1,1,,177606,5836953,0.99\n2347,O Segredo Do Universo,192,1,1,,192679,6315187,0.99\n2348,As Profecias,192,1,1,,232515,7657732,0.99\n2349,Mata Virgem,192,1,1,,142602,4690029,0.99\n2350,Sapato 36,192,1,1,,196702,6507301,0.99\n2351,Todo Mundo Explica,192,1,1,,134896,4449772,0.99\n2352,Que Luz É Essa,192,1,1,,165067,5620058,0.99\n2353,Diamante De Mendigo,192,1,1,,206053,6775101,0.99\n2354,Negócio É,192,1,1,,175464,5826775,0.99\n2355,\"Muita Estrela, Pouca Constelação\",192,1,1,,268068,8781021,0.99\n2356,Século XXI,192,1,1,,244897,8040563,0.99\n2357,Rock Das Aranhas (Ao Vivo) (Live),192,1,1,,231836,7591945,0.99\n2358,The Power Of Equality,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,243591,8148266,0.99\n2359,If You Have To Ask,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,216790,7199175,0.99\n2360,Breaking The Girl,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,295497,9805526,0.99\n2361,Funky Monks,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,323395,10708168,0.99\n2362,Suck My Kiss,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,217234,7129137,0.99\n2363,I Could Have Lied,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,244506,8088244,0.99\n2364,Mellowship Slinky In B Major,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,240091,7971384,0.99\n2365,The Righteous & The Wicked,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,248084,8134096,0.99\n2366,Give It Away,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,283010,9308997,0.99\n2367,Blood Sugar Sex Magik,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,271229,8940573,0.99\n2368,Under The Bridge,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,264359,8682716,0.99\n2369,Naked In The Rain,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,265717,8724674,0.99\n2370,Apache Rose Peacock,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,282226,9312588,0.99\n2371,The Greeting Song,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,193593,6346507,0.99\n2372,My Lovely Man,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,279118,9220114,0.99\n2373,Sir Psycho Sexy,193,1,4,Anthony Kiedis/Chad Smith/Flea/John Frusciante,496692,16354362,0.99\n2374,They're Red Hot,193,1,4,Robert Johnson,71941,2382220,0.99\n2375,By The Way,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",218017,7197430,0.99\n2376,Universally Speaking,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",259213,8501904,0.99\n2377,This Is The Place,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",257906,8469765,0.99\n2378,Dosed,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",312058,10235611,0.99\n2379,Don't Forget Me,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",277995,9107071,0.99\n2380,The Zephyr Song,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",232960,7690312,0.99\n2381,Can't Stop,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",269400,8872479,0.99\n2382,I Could Die For You,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",193906,6333311,0.99\n2383,Midnight,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",295810,9702450,0.99\n2384,Throw Away Your Television,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",224574,7483526,0.99\n2385,Cabron,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",218592,7458864,0.99\n2386,Tear,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",317413,10395500,0.99\n2387,On Mercury,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",208509,6834762,0.99\n2388,Minor Thing,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",217835,7148115,0.99\n2389,Warm Tape,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",256653,8358200,0.99\n2390,Venice Queen,194,1,1,\"Anthony Kiedis, Flea, John Frusciante, and Chad Smith\",369110,12280381,0.99\n2391,Around The World,195,1,1,Anthony Kiedis/Chad Smith/Flea/John Frusciante,238837,7859167,0.99\n2392,Parallel Universe,195,1,1,Red Hot Chili Peppers,270654,8958519,0.99\n2393,Scar Tissue,195,1,1,Red Hot Chili Peppers,217469,7153744,0.99\n2394,Otherside,195,1,1,Red Hot Chili Peppers,255973,8357989,0.99\n2395,Get On Top,195,1,1,Red Hot Chili Peppers,198164,6587883,0.99\n2396,Californication,195,1,1,Red Hot Chili Peppers,321671,10568999,0.99\n2397,Easily,195,1,1,Red Hot Chili Peppers,231418,7504534,0.99\n2398,Porcelain,195,1,1,Anthony Kiedis/Chad Smith/Flea/John Frusciante,163787,5278793,0.99\n2399,Emit Remmus,195,1,1,Red Hot Chili Peppers,240300,7901717,0.99\n2400,I Like Dirt,195,1,1,Red Hot Chili Peppers,157727,5225917,0.99\n2401,This Velvet Glove,195,1,1,Red Hot Chili Peppers,225280,7480537,0.99\n2402,Savior,195,1,1,Anthony Kiedis/Chad Smith/Flea/John Frusciante,292493,9551885,0.99\n2403,Purple Stain,195,1,1,Red Hot Chili Peppers,253440,8359971,0.99\n2404,Right On Time,195,1,1,Red Hot Chili Peppers,112613,3722219,0.99\n2405,Road Trippin',195,1,1,Red Hot Chili Peppers,205635,6685831,0.99\n2406,The Spirit Of Radio,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,299154,9862012,0.99\n2407,The Trees,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,285126,9345473,0.99\n2408,Something For Nothing,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,240770,7898395,0.99\n2409,Freewill,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,324362,10694110,0.99\n2410,Xanadu,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,667428,21753168,0.99\n2411,Bastille Day,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,280528,9264769,0.99\n2412,By-Tor And The Snow Dog,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,519888,17076397,0.99\n2413,Anthem,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,264515,8693343,0.99\n2414,Closer To The Heart,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,175412,5767005,0.99\n2415,2112 Overture,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,272718,8898066,0.99\n2416,The Temples Of Syrinx,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,133459,4360163,0.99\n2417,La Villa Strangiato,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,577488,19137855,0.99\n2418,Fly By Night,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,202318,6683061,0.99\n2419,Finding My Way,196,1,1,Geddy Lee And Alex Lifeson/Geddy Lee And Neil Peart/Rush,305528,9985701,0.99\n2420,Jingo,197,1,1,M.Babatunde Olantunji,592953,19736495,0.99\n2421,El Corazon Manda,197,1,1,E.Weiss,713534,23519583,0.99\n2422,La Puesta Del Sol,197,1,1,E.Weiss,628062,20614621,0.99\n2423,Persuasion,197,1,1,Carlos Santana,318432,10354751,0.99\n2424,As The Years Go by,197,1,1,Albert King,233064,7566829,0.99\n2425,Soul Sacrifice,197,1,1,Carlos Santana,296437,9801120,0.99\n2426,Fried Neckbones And Home Fries,197,1,1,W.Correa,638563,20939646,0.99\n2427,Santana Jam,197,1,1,Carlos Santana,882834,29207100,0.99\n2428,Evil Ways,198,1,1,,475402,15289235,0.99\n2429,We've Got To Get Together/Jingo,198,1,1,,1070027,34618222,0.99\n2430,Rock Me,198,1,1,,94720,3037596,0.99\n2431,Just Ain't Good Enough,198,1,1,,850259,27489067,0.99\n2432,Funky Piano,198,1,1,,934791,30200730,0.99\n2433,The Way You Do To Mer,198,1,1,,618344,20028702,0.99\n2434,Holding Back The Years,141,1,1,Mick Hucknall and Neil Moss,270053,8833220,0.99\n2435,Money's Too Tight To Mention,141,1,1,John and William Valentine,268408,8861921,0.99\n2436,The Right Thing,141,1,1,Mick Hucknall,262687,8624063,0.99\n2437,It's Only Love,141,1,1,Jimmy and Vella Cameron,232594,7659017,0.99\n2438,A New Flame,141,1,1,Mick Hucknall,237662,7822875,0.99\n2439,You've Got It,141,1,1,Mick Hucknall and Lamont Dozier,235232,7712845,0.99\n2440,If You Don't Know Me By Now,141,1,1,Kenny Gamble and Leon Huff,206524,6712634,0.99\n2441,Stars,141,1,1,Mick Hucknall,248137,8194906,0.99\n2442,Something Got Me Started,141,1,1,Mick Hucknall and Fritz McIntyre,239595,7997139,0.99\n2443,Thrill Me,141,1,1,Mick Hucknall and Fritz McIntyre,303934,10034711,0.99\n2444,Your Mirror,141,1,1,Mick Hucknall,240666,7893821,0.99\n2445,For Your Babies,141,1,1,Mick Hucknall,256992,8408803,0.99\n2446,So Beautiful,141,1,1,Mick Hucknall,298083,9837832,0.99\n2447,Angel,141,1,1,Carolyn Franklin and Sonny Saunders,240561,7880256,0.99\n2448,Fairground,141,1,1,Mick Hucknall,263888,8793094,0.99\n2449,Água E Fogo,199,1,1,Chico Amaral/Edgard Scandurra/Samuel Rosa,278987,9272272,0.99\n2450,Três Lados,199,1,1,Chico Amaral/Samuel Rosa,233665,7699609,0.99\n2451,Ela Desapareceu,199,1,1,Chico Amaral/Samuel Rosa,250122,8289200,0.99\n2452,Balada Do Amor Inabalável,199,1,1,Fausto Fawcett/Samuel Rosa,240613,8025816,0.99\n2453,Canção Noturna,199,1,1,Chico Amaral/Lelo Zanettik,238628,7874774,0.99\n2454,Muçulmano,199,1,1,\"Leão, Rodrigo F./Samuel Rosa\",249600,8270613,0.99\n2455,Maquinarama,199,1,1,Chico Amaral/Samuel Rosa,245629,8213710,0.99\n2456,Rebelião,199,1,1,Chico Amaral/Samuel Rosa,298527,9817847,0.99\n2457,A Última Guerra,199,1,1,\"Leão, Rodrigo F./Lô Borges/Samuel Rosa\",314723,10480391,0.99\n2458,Fica,199,1,1,Chico Amaral/Samuel Rosa,272169,8980972,0.99\n2459,Ali,199,1,1,Nando Reis/Samuel Rosa,306390,10110351,0.99\n2460,Preto Damião,199,1,1,Chico Amaral/Samuel Rosa,264568,8697658,0.99\n2461,É Uma Partida De Futebol,200,1,1,Samuel Rosa,1071,38747,0.99\n2462,Eu Disse A Ela,200,1,1,Samuel Rosa,254223,8479463,0.99\n2463,Zé Trindade,200,1,1,Samuel Rosa,247954,8331310,0.99\n2464,Garota Nacional,200,1,1,Samuel Rosa,317492,10511239,0.99\n2465,Tão Seu,200,1,1,Samuel Rosa,243748,8133126,0.99\n2466,Sem Terra,200,1,1,Samuel Rosa,279353,9196411,0.99\n2467,Os Exilados,200,1,1,Samuel Rosa,245551,8222095,0.99\n2468,Um Dia Qualquer,200,1,1,Samuel Rosa,292414,9805570,0.99\n2469,Los Pretos,200,1,1,Samuel Rosa,239229,8025667,0.99\n2470,Sul Da América,200,1,1,Samuel Rosa,254928,8484871,0.99\n2471,Poconé,200,1,1,Samuel Rosa,318406,10771610,0.99\n2472,Lucky 13,201,1,4,Billy Corgan,189387,6200617,0.99\n2473,Aeroplane Flies High,201,1,4,Billy Corgan,473391,15408329,0.99\n2474,Because You Are,201,1,4,Billy Corgan,226403,7405137,0.99\n2475,Slow Dawn,201,1,4,Billy Corgan,192339,6269057,0.99\n2476,Believe,201,1,4,James Iha,192940,6320652,0.99\n2477,My Mistake,201,1,4,Billy Corgan,240901,7843477,0.99\n2478,Marquis In Spades,201,1,4,Billy Corgan,192731,6304789,0.99\n2479,Here's To The Atom Bomb,201,1,4,Billy Corgan,266893,8763140,0.99\n2480,Sparrow,201,1,4,Billy Corgan,176822,5696989,0.99\n2481,Waiting,201,1,4,Billy Corgan,228336,7627641,0.99\n2482,Saturnine,201,1,4,Billy Corgan,229877,7523502,0.99\n2483,Rock On,201,1,4,David Cook,366471,12133825,0.99\n2484,Set The Ray To Jerry,201,1,4,Billy Corgan,249364,8215184,0.99\n2485,Winterlong,201,1,4,Billy Corgan,299389,9670616,0.99\n2486,Soot & Stars,201,1,4,Billy Corgan,399986,12866557,0.99\n2487,Blissed & Gone,201,1,4,Billy Corgan,286302,9305998,0.99\n2488,Siva,202,1,4,Billy Corgan,261172,8576622,0.99\n2489,Rhinocerous,202,1,4,Billy Corgan,353462,11526684,0.99\n2490,Drown,202,1,4,Billy Corgan,270497,8883496,0.99\n2491,Cherub Rock,202,1,4,Billy Corgan,299389,9786739,0.99\n2492,Today,202,1,4,Billy Corgan,202213,6596933,0.99\n2493,Disarm,202,1,4,Billy Corgan,198556,6508249,0.99\n2494,Landslide,202,1,4,Stevie Nicks,190275,6187754,0.99\n2495,Bullet With Butterfly Wings,202,1,4,Billy Corgan,257306,8431747,0.99\n2496,1979,202,1,4,Billy Corgan,263653,8728470,0.99\n2497,Zero,202,1,4,Billy Corgan,161123,5267176,0.99\n2498,\"Tonight, Tonight\",202,1,4,Billy Corgan,255686,8351543,0.99\n2499,Eye,202,1,4,Billy Corgan,294530,9784201,0.99\n2500,Ava Adore,202,1,4,Billy Corgan,261433,8590412,0.99\n2501,Perfect,202,1,4,Billy Corgan,203023,6734636,0.99\n2502,The Everlasting Gaze,202,1,4,Billy Corgan,242155,7844404,0.99\n2503,Stand Inside Your Love,202,1,4,Billy Corgan,253753,8270113,0.99\n2504,Real Love,202,1,4,Billy Corgan,250697,8025896,0.99\n2505,[Untitled],202,1,4,Billy Corgan,231784,7689713,0.99\n2506,Nothing To Say,203,1,1,Chris Cornell/Kim Thayil,238027,7744833,0.99\n2507,Flower,203,1,1,Chris Cornell/Kim Thayil,208822,6830732,0.99\n2508,Loud Love,203,1,1,Chris Cornell,297456,9660953,0.99\n2509,Hands All Over,203,1,1,Chris Cornell/Kim Thayil,362475,11893108,0.99\n2510,Get On The Snake,203,1,1,Chris Cornell/Kim Thayil,225123,7313744,0.99\n2511,Jesus Christ Pose,203,1,1,Ben Shepherd/Chris Cornell/Kim Thayil/Matt Cameron,352966,11739886,0.99\n2512,Outshined,203,1,1,Chris Cornell,312476,10274629,0.99\n2513,Rusty Cage,203,1,1,Chris Cornell,267728,8779485,0.99\n2514,Spoonman,203,1,1,Chris Cornell,248476,8289906,0.99\n2515,The Day I Tried To Live,203,1,1,Chris Cornell,321175,10507137,0.99\n2516,Black Hole Sun,203,1,1,Soundgarden,320365,10425229,0.99\n2517,Fell On Black Days,203,1,1,Chris Cornell,282331,9256082,0.99\n2518,Pretty Noose,203,1,1,Chris Cornell,253570,8317931,0.99\n2519,Burden In My Hand,203,1,1,Chris Cornell,292153,9659911,0.99\n2520,Blow Up The Outside World,203,1,1,Chris Cornell,347898,11379527,0.99\n2521,Ty Cobb,203,1,1,Ben Shepherd/Chris Cornell,188786,6233136,0.99\n2522,Bleed Together,203,1,1,Chris Cornell,232202,7597074,0.99\n2523,Morning Dance,204,1,2,Jay Beckenstein,238759,8101979,0.99\n2524,Jubilee,204,1,2,Jeremy Wall,275147,9151846,0.99\n2525,Rasul,204,1,2,Jeremy Wall,238315,7854737,0.99\n2526,Song For Lorraine,204,1,2,Jay Beckenstein,240091,8101723,0.99\n2527,Starburst,204,1,2,Jeremy Wall,291500,9768399,0.99\n2528,Heliopolis,204,1,2,Jay Beckenstein,338729,11365655,0.99\n2529,It Doesn't Matter,204,1,2,Chet Catallo,270027,9034177,0.99\n2530,Little Linda,204,1,2,Jeremy Wall,264019,8958743,0.99\n2531,End Of Romanticism,204,1,2,Rick Strauss,320078,10553155,0.99\n2532,The House Is Rockin',205,1,6,Doyle Bramhall/Stevie Ray Vaughan,144352,4706253,0.99\n2533,Crossfire,205,1,6,B. Carter/C. Layton/R. Ellsworth/R. Wynans/T. Shannon,251219,8238033,0.99\n2534,Tightrope,205,1,6,Doyle Bramhall/Stevie Ray Vaughan,281155,9254906,0.99\n2535,Let Me Love You Baby,205,1,6,Willie Dixon,164127,5378455,0.99\n2536,Leave My Girl Alone,205,1,6,B. Guy,256365,8438021,0.99\n2537,Travis Walk,205,1,6,Stevie Ray Vaughan,140826,4650979,0.99\n2538,Wall Of Denial,205,1,6,Doyle Bramhall/Stevie Ray Vaughan,336927,11085915,0.99\n2539,Scratch-N-Sniff,205,1,6,Doyle Bramhall/Stevie Ray Vaughan,163422,5353627,0.99\n2540,Love Me Darlin',205,1,6,C. Burnett,201586,6650869,0.99\n2541,Riviera Paradise,205,1,6,Stevie Ray Vaughan,528692,17232776,0.99\n2542,Dead And Bloated,206,1,1,R. DeLeo/Weiland,310386,10170433,0.99\n2543,Sex Type Thing,206,1,1,D. DeLeo/Kretz/Weiland,218723,7102064,0.99\n2544,Wicked Garden,206,1,1,D. DeLeo/R. DeLeo/Weiland,245368,7989505,0.99\n2545,No Memory,206,1,1,Dean Deleo,80613,2660859,0.99\n2546,Sin,206,1,1,R. DeLeo/Weiland,364800,12018823,0.99\n2547,Naked Sunday,206,1,1,D. DeLeo/Kretz/R. DeLeo/Weiland,229720,7444201,0.99\n2548,Creep,206,1,1,R. DeLeo/Weiland,333191,10894988,0.99\n2549,Piece Of Pie,206,1,1,R. DeLeo/Weiland,324623,10605231,0.99\n2550,Plush,206,1,1,R. DeLeo/Weiland,314017,10229848,0.99\n2551,Wet My Bed,206,1,1,R. DeLeo/Weiland,96914,3198627,0.99\n2552,Crackerman,206,1,1,Kretz/R. DeLeo/Weiland,194403,6317361,0.99\n2553,Where The River Goes,206,1,1,D. DeLeo/Kretz/Weiland,505991,16468904,0.99\n2554,Soldier Side - Intro,207,1,3,\"Dolmayan, John/Malakian, Daron/Odadjian, Shavo\",63764,2056079,0.99\n2555,B.Y.O.B.,207,1,3,\"Tankian, Serj\",255555,8407935,0.99\n2556,Revenga,207,1,3,\"Tankian, Serj\",228127,7503805,0.99\n2557,Cigaro,207,1,3,\"Tankian, Serj\",131787,4321705,0.99\n2558,Radio/Video,207,1,3,\"Dolmayan, John/Malakian, Daron/Odadjian, Shavo\",249312,8224917,0.99\n2559,This Cocaine Makes Me Feel Like I'm On This Song,207,1,3,\"Tankian, Serj\",128339,4185193,0.99\n2560,Violent Pornography,207,1,3,\"Dolmayan, John/Malakian, Daron/Odadjian, Shavo\",211435,6985960,0.99\n2561,Question!,207,1,3,\"Tankian, Serj\",200698,6616398,0.99\n2562,Sad Statue,207,1,3,\"Tankian, Serj\",205897,6733449,0.99\n2563,Old School Hollywood,207,1,3,\"Dolmayan, John/Malakian, Daron/Odadjian, Shavo\",176953,5830258,0.99\n2564,Lost in Hollywood,207,1,3,\"Tankian, Serj\",320783,10535158,0.99\n2565,The Sun Road,208,1,1,\"Terry Bozzio, Steve Stevens, Tony Levin\",880640,29008407,0.99\n2566,Dark Corners,208,1,1,\"Terry Bozzio, Steve Stevens, Tony Levin\",513541,16839223,0.99\n2567,Duende,208,1,1,\"Terry Bozzio, Steve Stevens, Tony Levin\",447582,14956771,0.99\n2568,Black Light Syndrome,208,1,1,\"Terry Bozzio, Steve Stevens, Tony Levin\",526471,17300835,0.99\n2569,Falling in Circles,208,1,1,\"Terry Bozzio, Steve Stevens, Tony Levin\",549093,18263248,0.99\n2570,Book of Hours,208,1,1,\"Terry Bozzio, Steve Stevens, Tony Levin\",583366,19464726,0.99\n2571,Chaos-Control,208,1,1,\"Terry Bozzio, Steve Stevens, Tony Levin\",529841,17455568,0.99\n2572,Midnight From The Inside Out,209,1,6,Chris Robinson/Rich Robinson,286981,9442157,0.99\n2573,Sting Me,209,1,6,Chris Robinson/Rich Robinson,268094,8813561,0.99\n2574,Thick & Thin,209,1,6,Chris Robinson/Rich Robinson,222720,7284377,0.99\n2575,Greasy Grass River,209,1,6,Chris Robinson/Rich Robinson,218749,7157045,0.99\n2576,Sometimes Salvation,209,1,6,Chris Robinson/Rich Robinson,389146,12749424,0.99\n2577,Cursed Diamonds,209,1,6,Chris Robinson/Rich Robinson,368300,12047978,0.99\n2578,Miracle To Me,209,1,6,Chris Robinson/Rich Robinson,372636,12222116,0.99\n2579,Wiser Time,209,1,6,Chris Robinson/Rich Robinson,459990,15161907,0.99\n2580,Girl From A Pawnshop,209,1,6,Chris Robinson/Rich Robinson,404688,13250848,0.99\n2581,Cosmic Fiend,209,1,6,Chris Robinson/Rich Robinson,308401,10115556,0.99\n2582,Black Moon Creeping,210,1,6,Chris Robinson/Rich Robinson,359314,11740886,0.99\n2583,High Head Blues,210,1,6,Chris Robinson/Rich Robinson,371879,12227998,0.99\n2584,Title Song,210,1,6,Chris Robinson/Rich Robinson,505521,16501316,0.99\n2585,She Talks To Angels,210,1,6,Chris Robinson/Rich Robinson,361978,11837342,0.99\n2586,Twice As Hard,210,1,6,Chris Robinson/Rich Robinson,275565,9008067,0.99\n2587,Lickin',210,1,6,Chris Robinson/Rich Robinson,314409,10331216,0.99\n2588,Soul Singing,210,1,6,Chris Robinson/Rich Robinson,233639,7672489,0.99\n2589,Hard To Handle,210,1,6,A.Isbell/A.Jones/O.Redding,206994,6786304,0.99\n2590,Remedy,210,1,6,Chris Robinson/Rich Robinson,337084,11049098,0.99\n2591,White Riot,211,1,4,Joe Strummer/Mick Jones,118726,3922819,0.99\n2592,Remote Control,211,1,4,Joe Strummer/Mick Jones,180297,5949647,0.99\n2593,Complete Control,211,1,4,Joe Strummer/Mick Jones,192653,6272081,0.99\n2594,Clash City Rockers,211,1,4,Joe Strummer/Mick Jones,227500,7555054,0.99\n2595,(White Man) In Hammersmith Palais,211,1,4,Joe Strummer/Mick Jones,240640,7883532,0.99\n2596,Tommy Gun,211,1,4,Joe Strummer/Mick Jones,195526,6399872,0.99\n2597,English Civil War,211,1,4,Mick Jones/Traditional arr. Joe Strummer,156708,5111226,0.99\n2598,I Fought The Law,211,1,4,Sonny Curtis,159764,5245258,0.99\n2599,London Calling,211,1,4,Joe Strummer/Mick Jones,199706,6569007,0.99\n2600,Train In Vain,211,1,4,Joe Strummer/Mick Jones,189675,6329877,0.99\n2601,Bankrobber,211,1,4,Joe Strummer/Mick Jones,272431,9067323,0.99\n2602,The Call Up,211,1,4,The Clash,324336,10746937,0.99\n2603,Hitsville UK,211,1,4,The Clash,261433,8606887,0.99\n2604,The Magnificent Seven,211,1,4,The Clash,268486,8889821,0.99\n2605,This Is Radio Clash,211,1,4,The Clash,249756,8366573,0.99\n2606,Know Your Rights,211,1,4,The Clash,217678,7195726,0.99\n2607,Rock The Casbah,211,1,4,The Clash,222145,7361500,0.99\n2608,Should I Stay Or Should I Go,211,1,4,The Clash,187219,6188688,0.99\n2609,War (The Process),212,1,1,Billy Duffy/Ian Astbury,252630,8254842,0.99\n2610,The Saint,212,1,1,Billy Duffy/Ian Astbury,216215,7061584,0.99\n2611,Rise,212,1,1,Billy Duffy/Ian Astbury,219088,7106195,0.99\n2612,Take The Power,212,1,1,Billy Duffy/Ian Astbury,235755,7650012,0.99\n2613,Breathe,212,1,1,Billy Duffy/Ian Astbury/Marti Frederiksen/Mick Jones,299781,9742361,0.99\n2614,Nico,212,1,1,Billy Duffy/Ian Astbury,289488,9412323,0.99\n2615,American Gothic,212,1,1,Billy Duffy/Ian Astbury,236878,7739840,0.99\n2616,Ashes And Ghosts,212,1,1,Billy Duffy/Bob Rock/Ian Astbury,300591,9787692,0.99\n2617,Shape The Sky,212,1,1,Billy Duffy/Ian Astbury,209789,6885647,0.99\n2618,Speed Of Light,212,1,1,Billy Duffy/Bob Rock/Ian Astbury,262817,8563352,0.99\n2619,True Believers,212,1,1,Billy Duffy/Ian Astbury,308009,9981359,0.99\n2620,My Bridges Burn,212,1,1,Billy Duffy/Ian Astbury,231862,7571370,0.99\n2621,She Sells Sanctuary,213,1,1,,253727,8368634,0.99\n2622,Fire Woman,213,1,1,,312790,10196995,0.99\n2623,Lil' Evil,213,1,1,,165825,5419655,0.99\n2624,Spirit Walker,213,1,1,,230060,7555897,0.99\n2625,The Witch,213,1,1,,258768,8725403,0.99\n2626,Revolution,213,1,1,,256026,8371254,0.99\n2627,Wild Hearted Son,213,1,1,,266893,8670550,0.99\n2628,Love Removal Machine,213,1,1,,257619,8412167,0.99\n2629,Rain,213,1,1,,236669,7788461,0.99\n2630,Edie (Ciao Baby),213,1,1,,241632,7846177,0.99\n2631,Heart Of Soul,213,1,1,,274207,8967257,0.99\n2632,Love,213,1,1,,326739,10729824,0.99\n2633,Wild Flower,213,1,1,,215536,7084321,0.99\n2634,Go West,213,1,1,,238158,7777749,0.99\n2635,Resurrection Joe,213,1,1,,255451,8532840,0.99\n2636,Sun King,213,1,1,,368431,12010865,0.99\n2637,Sweet Soul Sister,213,1,1,,212009,6889883,0.99\n2638,Earth Mofo,213,1,1,,282200,9204581,0.99\n2639,Break on Through,214,1,1,\"Robby Krieger, Ray Manzarek, John Densmore, Jim Morrison\",149342,4943144,0.99\n2640,Soul Kitchen,214,1,1,\"Robby Krieger, Ray Manzarek, John Densmore, Jim Morrison\",215066,7040865,0.99\n2641,The Crystal Ship,214,1,1,\"Robby Krieger, Ray Manzarek, John Densmore, Jim Morrison\",154853,5052658,0.99\n2642,Twentienth Century Fox,214,1,1,\"Robby Krieger, Ray Manzarek, John Densmore, Jim Morrison\",153913,5069211,0.99\n2643,Alabama Song,214,1,1,Weill-Brecht,200097,6563411,0.99\n2644,Light My Fire,214,1,1,\"Robby Krieger, Ray Manzarek, John Densmore, Jim Morrison\",428329,13963351,0.99\n2645,Back Door Man,214,1,1,\"Willie Dixon, C. Burnett\",214360,7035636,0.99\n2646,I Looked At You,214,1,1,\"Robby Krieger, Ray Manzarek, John Densmore, Jim Morrison\",142080,4663988,0.99\n2647,End Of The Night,214,1,1,\"Robby Krieger, Ray Manzarek, John Densmore, Jim Morrison\",172695,5589732,0.99\n2648,Take It As It Comes,214,1,1,\"Robby Krieger, Ray Manzarek, John Densmore, Jim Morrison\",137168,4512656,0.99\n2649,The End,214,1,1,\"Robby Krieger, Ray Manzarek, John Densmore, Jim Morrison\",701831,22927336,0.99\n2650,Roxanne,215,1,1,G M Sumner,192992,6330159,0.99\n2651,Can't Stand Losing You,215,1,1,G M Sumner,181159,5971983,0.99\n2652,Message in a Bottle,215,1,1,G M Sumner,291474,9647829,0.99\n2653,Walking on the Moon,215,1,1,G M Sumner,302080,10019861,0.99\n2654,Don't Stand so Close to Me,215,1,1,G M Sumner,241031,7956658,0.99\n2655,\"De Do Do Do, De Da Da Da\",215,1,1,G M Sumner,247196,8227075,0.99\n2656,Every Little Thing She Does is Magic,215,1,1,G M Sumner,261120,8646853,0.99\n2657,Invisible Sun,215,1,1,G M Sumner,225593,7304320,0.99\n2658,Spirit's in the Material World,215,1,1,G M Sumner,181133,5986622,0.99\n2659,Every Breath You Take,215,1,1,G M Sumner,254615,8364520,0.99\n2660,King Of Pain,215,1,1,G M Sumner,300512,9880303,0.99\n2661,Wrapped Around Your Finger,215,1,1,G M Sumner,315454,10361490,0.99\n2662,Don't Stand So Close to Me '86,215,1,1,G M Sumner,293590,9636683,0.99\n2663,Message in a Bottle (new classic rock mix),215,1,1,G M Sumner,290951,9640349,0.99\n2664,Time Is On My Side,216,1,1,Jerry Ragavoy,179983,5855836,0.99\n2665,Heart Of Stone,216,1,1,Jagger/Richards,164493,5329538,0.99\n2666,Play With Fire,216,1,1,Nanker Phelge,132022,4265297,0.99\n2667,Satisfaction,216,1,1,Jagger/Richards,226612,7398766,0.99\n2668,As Tears Go By,216,1,1,Jagger/Richards/Oldham,164284,5357350,0.99\n2669,Get Off Of My Cloud,216,1,1,Jagger/Richards,176013,5719514,0.99\n2670,Mother's Little Helper,216,1,1,Jagger/Richards,167549,5422434,0.99\n2671,19th Nervous Breakdown,216,1,1,Jagger/Richards,237923,7742984,0.99\n2672,Paint It Black,216,1,1,Jagger/Richards,226063,7442888,0.99\n2673,Under My Thumb,216,1,1,Jagger/Richards,221387,7371799,0.99\n2674,Ruby Tuesday,216,1,1,Jagger/Richards,197459,6433467,0.99\n2675,Let's Spend The Night Together,216,1,1,Jagger/Richards,217495,7137048,0.99\n2676,Intro,217,1,1,Jagger/Richards,49737,1618591,0.99\n2677,You Got Me Rocking,217,1,1,Jagger/Richards,205766,6734385,0.99\n2678,Gimmie Shelters,217,1,1,Jagger/Richards,382119,12528764,0.99\n2679,Flip The Case,217,1,1,Jagger/Richards,252421,8336591,0.99\n2680,Memory Motel,217,1,1,Jagger/Richards,365844,11982431,0.99\n2681,Corinna,217,1,1,Jesse Ed Davis III/Taj Mahal,257488,8449471,0.99\n2682,Saint Of Me,217,1,1,Jagger/Richards,325694,10725160,0.99\n2683,Wainting On A Friend,217,1,1,Jagger/Richards,302497,9978046,0.99\n2684,Sister Morphine,217,1,1,Faithfull/Jagger/Richards,376215,12345289,0.99\n2685,Live With Me,217,1,1,Jagger/Richards,234893,7709006,0.99\n2686,Respectable,217,1,1,Jagger/Richards,215693,7099669,0.99\n2687,Thief In The Night,217,1,1,De Beauport/Jagger/Richards,337266,10952756,0.99\n2688,The Last Time,217,1,1,Jagger/Richards,287294,9498758,0.99\n2689,Out Of Control,217,1,1,Jagger/Richards,479242,15749289,0.99\n2690,Love Is Strong,218,1,1,Jagger/Richards,230896,7639774,0.99\n2691,You Got Me Rocking,218,1,1,Jagger/Richards,215928,7162159,0.99\n2692,Sparks Will Fly,218,1,1,Jagger/Richards,196466,6492847,0.99\n2693,The Worst,218,1,1,Jagger/Richards,144613,4750094,0.99\n2694,New Faces,218,1,1,Jagger/Richards,172146,5689122,0.99\n2695,Moon Is Up,218,1,1,Jagger/Richards,222119,7366316,0.99\n2696,Out Of Tears,218,1,1,Jagger/Richards,327418,10677236,0.99\n2697,I Go Wild,218,1,1,Jagger/Richards,264019,8630833,0.99\n2698,Brand New Car,218,1,1,Jagger/Richards,256052,8459344,0.99\n2699,Sweethearts Together,218,1,1,Jagger/Richards,285492,9550459,0.99\n2700,Suck On The Jugular,218,1,1,Jagger/Richards,268225,8920566,0.99\n2701,Blinded By Rainbows,218,1,1,Jagger/Richards,273946,8971343,0.99\n2702,Baby Break It Down,218,1,1,Jagger/Richards,249417,8197309,0.99\n2703,Thru And Thru,218,1,1,Jagger/Richards,375092,12175406,0.99\n2704,Mean Disposition,218,1,1,Jagger/Richards,249155,8273602,0.99\n2705,Walking Wounded,219,1,4,The Tea Party,277968,9184345,0.99\n2706,Temptation,219,1,4,The Tea Party,205087,6711943,0.99\n2707,The Messenger,219,1,4,Daniel Lanois,212062,6975437,0.99\n2708,Psychopomp,219,1,4,The Tea Party,315559,10295199,0.99\n2709,Sister Awake,219,1,4,The Tea Party,343875,11299407,0.99\n2710,The Bazaar,219,1,4,The Tea Party,222458,7245691,0.99\n2711,Save Me (Remix),219,1,4,The Tea Party,396303,13053839,0.99\n2712,Fire In The Head,219,1,4,The Tea Party,306337,10005675,0.99\n2713,Release,219,1,4,The Tea Party,244114,8014606,0.99\n2714,Heaven Coming Down,219,1,4,The Tea Party,241867,7846459,0.99\n2715,The River (Remix),219,1,4,The Tea Party,343170,11193268,0.99\n2716,Babylon,219,1,4,The Tea Party,169795,5568808,0.99\n2717,Waiting On A Sign,219,1,4,The Tea Party,261903,8558590,0.99\n2718,Life Line,219,1,4,The Tea Party,277786,9082773,0.99\n2719,Paint It Black,219,1,4,Keith Richards/Mick Jagger,214752,7101572,0.99\n2720,Temptation,220,1,4,The Tea Party,205244,6719465,0.99\n2721,Army Ants,220,1,4,The Tea Party,215405,7075838,0.99\n2722,Psychopomp,220,1,4,The Tea Party,317231,10351778,0.99\n2723,Gyroscope,220,1,4,The Tea Party,177711,5810323,0.99\n2724,Alarum,220,1,4,The Tea Party,298187,9712545,0.99\n2725,Release,220,1,4,The Tea Party,266292,8725824,0.99\n2726,Transmission,220,1,4,The Tea Party,317257,10351152,0.99\n2727,Babylon,220,1,4,The Tea Party,292466,9601786,0.99\n2728,Pulse,220,1,4,The Tea Party,250253,8183872,0.99\n2729,Emerald,220,1,4,The Tea Party,289750,9543789,0.99\n2730,Aftermath,220,1,4,The Tea Party,343745,11085607,0.99\n2731,I Can't Explain,221,1,1,Pete Townshend,125152,4082896,0.99\n2732,\"Anyway, Anyhow, Anywhere\",221,1,1,\"Pete Townshend, Roger Daltrey\",161253,5234173,0.99\n2733,My Generation,221,1,1,John Entwistle/Pete Townshend,197825,6446634,0.99\n2734,Substitute,221,1,1,Pete Townshend,228022,7409995,0.99\n2735,I'm A Boy,221,1,1,Pete Townshend,157126,5120605,0.99\n2736,Boris The Spider,221,1,1,John Entwistle,149472,4835202,0.99\n2737,Happy Jack,221,1,1,Pete Townshend,132310,4353063,0.99\n2738,Pictures Of Lily,221,1,1,Pete Townshend,164414,5329751,0.99\n2739,I Can See For Miles,221,1,1,Pete Townshend,262791,8604989,0.99\n2740,Magic Bus,221,1,1,Pete Townshend,197224,6452700,0.99\n2741,Pinball Wizard,221,1,1,John Entwistle/Pete Townshend,181890,6055580,0.99\n2742,The Seeker,221,1,1,Pete Townshend,204643,6736866,0.99\n2743,Baba O'Riley,221,1,1,John Entwistle/Pete Townshend,309472,10141660,0.99\n2744,Won't Get Fooled Again (Full Length Version),221,1,1,John Entwistle/Pete Townshend,513750,16855521,0.99\n2745,Let's See Action,221,1,1,Pete Townshend,243513,8078418,0.99\n2746,5.15,221,1,1,Pete Townshend,289619,9458549,0.99\n2747,Join Together,221,1,1,Pete Townshend,262556,8602485,0.99\n2748,Squeeze Box,221,1,1,Pete Townshend,161280,5256508,0.99\n2749,Who Are You (Single Edit Version),221,1,1,John Entwistle/Pete Townshend,299232,9900469,0.99\n2750,You Better You Bet,221,1,1,Pete Townshend,338520,11160877,0.99\n2751,Primavera,222,1,7,Genival Cassiano/Silvio Rochael,126615,4152604,0.99\n2752,Chocolate,222,1,7,Tim Maia,194690,6411587,0.99\n2753,Azul Da Cor Do Mar,222,1,7,Tim Maia,197955,6475007,0.99\n2754,O Descobridor Dos Sete Mares,222,1,7,Gilson Mendonça/Michel,262974,8749583,0.99\n2755,Até Que Enfim Encontrei Você,222,1,7,Tim Maia,105064,3477751,0.99\n2756,Coroné Antonio Bento,222,1,7,\"Do Vale, João/Luiz Wanderley\",131317,4340326,0.99\n2757,New Love,222,1,7,Tim Maia,237897,7786824,0.99\n2758,Não Vou Ficar,222,1,7,Tim Maia,172068,5642919,0.99\n2759,Música No Ar,222,1,7,Tim Maia,158511,5184891,0.99\n2760,Salve Nossa Senhora,222,1,7,Carlos Imperial/Edardo Araújo,115461,3827629,0.99\n2761,Você Fugiu,222,1,7,Genival Cassiano,238367,7971147,0.99\n2762,Cristina Nº 2,222,1,7,Carlos Imperial/Tim Maia,90148,2978589,0.99\n2763,Compadre,222,1,7,Tim Maia,171389,5631446,0.99\n2764,Over Again,222,1,7,Tim Maia,200489,6612634,0.99\n2765,Réu Confesso,222,1,7,Tim Maia,217391,7189874,0.99\n2766,O Que Me Importa,223,1,7,,153155,4977852,0.99\n2767,Gostava Tanto De Você,223,1,7,,253805,8380077,0.99\n2768,Você,223,1,7,,242599,7911702,0.99\n2769,Não Quero Dinheiro,223,1,7,,152607,5031797,0.99\n2770,Eu Amo Você,223,1,7,,242782,7914628,0.99\n2771,A Festa Do Santo Reis,223,1,7,,159791,5204995,0.99\n2772,I Don't Know What To Do With Myself,223,1,7,,221387,7251478,0.99\n2773,Padre Cícero,223,1,7,,139598,4581685,0.99\n2774,Nosso Adeus,223,1,7,,206471,6793270,0.99\n2775,Canário Do Reino,223,1,7,,139337,4552858,0.99\n2776,Preciso Ser Amado,223,1,7,,174001,5618895,0.99\n2777,Balanço,223,1,7,,209737,6890327,0.99\n2778,Preciso Aprender A Ser Só,223,1,7,,162220,5213894,0.99\n2779,Esta É A Canção,223,1,7,,184450,6069933,0.99\n2780,Formigueiro,223,1,7,,252943,8455132,0.99\n2781,Comida,224,1,4,Titãs,322612,10786578,0.99\n2782,Go Back,224,1,4,Titãs,230504,7668899,0.99\n2783,Prá Dizer Adeus,224,1,4,Titãs,222484,7382048,0.99\n2784,Família,224,1,4,Titãs,218331,7267458,0.99\n2785,Os Cegos Do Castelo,224,1,4,Titãs,296829,9868187,0.99\n2786,O Pulso,224,1,4,Titãs,199131,6566998,0.99\n2787,Marvin,224,1,4,Titãs,264359,8741444,0.99\n2788,Nem 5 Minutos Guardados,224,1,4,Titãs,245995,8143797,0.99\n2789,Flores,224,1,4,Titãs,215510,7148017,0.99\n2790,Palavras,224,1,4,Titãs,158458,5285715,0.99\n2791,Hereditário,224,1,4,Titãs,151693,5020547,0.99\n2792,A Melhor Forma,224,1,4,Titãs,191503,6349938,0.99\n2793,Cabeça Dinossauro,224,1,4,Titãs,37120,1220930,0.99\n2794,32 Dentes,224,1,4,Titãs,184946,6157904,0.99\n2795,Bichos Escrotos (Vinheta),224,1,4,Titãs,104986,3503755,0.99\n2796,Não Vou Lutar,224,1,4,Titãs,189988,6308613,0.99\n2797,Homem Primata (Vinheta),224,1,4,Titãs,34168,1124909,0.99\n2798,Homem Primata,224,1,4,Titãs,195500,6486470,0.99\n2799,Polícia (Vinheta),224,1,4,Titãs,56111,1824213,0.99\n2800,Querem Meu Sangue,224,1,4,Titãs,212401,7069773,0.99\n2801,Diversão,224,1,4,Titãs,285936,9531268,0.99\n2802,Televisão,224,1,4,Titãs,293668,9776548,0.99\n2803,Sonifera Ilha,225,1,4,Branco Mello/Carlos Barmack/Ciro Pessoa/Marcelo Fromer/Toni Belloto,170684,5678290,0.99\n2804,Lugar Nenhum,225,1,4,Arnaldo Antunes/Charles Gavin/Marcelo Fromer/Sérgio Britto/Toni Bellotto,195840,6472780,0.99\n2805,Sua Impossivel Chance,225,1,4,Nando Reis,246622,8073248,0.99\n2806,Desordem,225,1,4,Charles Gavin/Marcelo Fromer/Sérgio Britto,213289,7067340,0.99\n2807,Não Vou Me Adaptar,225,1,4,Arnaldo Antunes,221831,7304656,0.99\n2808,Domingo,225,1,4,Sérgio Britto/Toni Bellotto,208613,6883180,0.99\n2809,Amanhã Não Se Sabe,225,1,4,Sérgio Britto,189440,6243967,0.99\n2810,Caras Como Eu,225,1,4,Toni Bellotto,183092,5999048,0.99\n2811,Senhora E Senhor,225,1,4,Arnaldo Anutnes/Marcelo Fromer/Paulo Miklos,203702,6733733,0.99\n2812,Era Uma Vez,225,1,4,Arnaldo Anutnes/Branco Mello/Marcelo Fromer/Sergio Brotto/Toni Bellotto,224261,7453156,0.99\n2813,Miséria,225,1,4,\"Arnaldo Antunes/Britto, SergioMiklos, Paulo\",262191,8727645,0.99\n2814,Insensível,225,1,4,Sérgio Britto,207830,6893664,0.99\n2815,Eu E Ela,225,1,4,Nando Reis,276035,9138846,0.99\n2816,Toda Cor,225,1,4,Ciro Pressoa/Marcelo Fromer,209084,6939176,0.99\n2817,É Preciso Saber Viver,225,1,4,Erasmo Carlos/Roberto Carlos,251115,8271418,0.99\n2818,Senhor Delegado/Eu Não Aguento,225,1,4,Antonio Lopes,156656,5277983,0.99\n2819,Battlestar Galactica: The Story So Far,226,3,18,,2622250,490750393,1.99\n2820,Occupation / Precipice,227,3,19,,5286953,1054423946,1.99\n2821,\"Exodus, Pt. 1\",227,3,19,,2621708,475079441,1.99\n2822,\"Exodus, Pt. 2\",227,3,19,,2618000,466820021,1.99\n2823,Collaborators,227,3,19,,2626626,483484911,1.99\n2824,Torn,227,3,19,,2631291,495262585,1.99\n2825,A Measure of Salvation,227,3,18,,2563938,489715554,1.99\n2826,Hero,227,3,18,,2713755,506896959,1.99\n2827,Unfinished Business,227,3,18,,2622038,528499160,1.99\n2828,The Passage,227,3,18,,2623875,490375760,1.99\n2829,The Eye of Jupiter,227,3,18,,2618750,517909587,1.99\n2830,Rapture,227,3,18,,2624541,508406153,1.99\n2831,Taking a Break from All Your Worries,227,3,18,,2624207,492700163,1.99\n2832,The Woman King,227,3,18,,2626376,552893447,1.99\n2833,A Day In the Life,227,3,18,,2620245,462818231,1.99\n2834,Dirty Hands,227,3,18,,2627961,537648614,1.99\n2835,Maelstrom,227,3,18,,2622372,514154275,1.99\n2836,The Son Also Rises,227,3,18,,2621830,499258498,1.99\n2837,\"Crossroads, Pt. 1\",227,3,20,,2622622,486233524,1.99\n2838,\"Crossroads, Pt. 2\",227,3,20,,2869953,497335706,1.99\n2839,Genesis,228,3,19,,2611986,515671080,1.99\n2840,Don't Look Back,228,3,21,,2571154,493628775,1.99\n2841,One Giant Leap,228,3,21,,2607649,521616246,1.99\n2842,Collision,228,3,21,,2605480,526182322,1.99\n2843,Hiros,228,3,21,,2533575,488835454,1.99\n2844,Better Halves,228,3,21,,2573031,549353481,1.99\n2845,Nothing to Hide,228,3,19,,2605647,510058181,1.99\n2846,Seven Minutes to Midnight,228,3,21,,2613988,515590682,1.99\n2847,Homecoming,228,3,21,,2601351,516015339,1.99\n2848,Six Months Ago,228,3,19,,2602852,505133869,1.99\n2849,Fallout,228,3,21,,2594761,501145440,1.99\n2850,The Fix,228,3,21,,2600266,507026323,1.99\n2851,Distractions,228,3,21,,2590382,537111289,1.99\n2852,Run!,228,3,21,,2602602,542936677,1.99\n2853,Unexpected,228,3,21,,2598139,511777758,1.99\n2854,Company Man,228,3,21,,2601226,493168135,1.99\n2855,Company Man,228,3,21,,2601101,503786316,1.99\n2856,Parasite,228,3,21,,2602727,487461520,1.99\n2857,A Tale of Two Cities,229,3,19,,2636970,513691652,1.99\n2858,\"Lost (Pilot, Part 1) [Premiere]\",230,3,19,,2548875,217124866,1.99\n2859,\"Man of Science, Man of Faith (Premiere)\",231,3,19,,2612250,543342028,1.99\n2860,Adrift,231,3,19,,2564958,502663995,1.99\n2861,\"Lost (Pilot, Part 2)\",230,3,19,,2436583,204995876,1.99\n2862,The Glass Ballerina,229,3,21,,2637458,535729216,1.99\n2863,Further Instructions,229,3,19,,2563980,502041019,1.99\n2864,Orientation,231,3,19,,2609083,500600434,1.99\n2865,Tabula Rasa,230,3,19,,2627105,210526410,1.99\n2866,Every Man for Himself,229,3,21,,2637387,513803546,1.99\n2867,Everybody Hates Hugo,231,3,19,,2609192,498163145,1.99\n2868,Walkabout,230,3,19,,2587370,207748198,1.99\n2869,...And Found,231,3,19,,2563833,500330548,1.99\n2870,The Cost of Living,229,3,19,,2637500,505647192,1.99\n2871,White Rabbit,230,3,19,,2571965,201654606,1.99\n2872,Abandoned,231,3,19,,2587041,537348711,1.99\n2873,House of the Rising Sun,230,3,19,,2590032,210379525,1.99\n2874,I Do,229,3,19,,2627791,504676825,1.99\n2875,Not In Portland,229,3,21,,2637303,499061234,1.99\n2876,Not In Portland,229,3,21,,2637345,510546847,1.99\n2877,The Moth,230,3,19,,2631327,228896396,1.99\n2878,The Other 48 Days,231,3,19,,2610625,535256753,1.99\n2879,Collision,231,3,19,,2564916,475656544,1.99\n2880,Confidence Man,230,3,19,,2615244,223756475,1.99\n2881,Flashes Before Your Eyes,229,3,21,,2636636,537760755,1.99\n2882,Lost Survival Guide,229,3,21,,2632590,486675063,1.99\n2883,Solitary,230,3,19,,2612894,207045178,1.99\n2884,What Kate Did,231,3,19,,2610250,484583988,1.99\n2885,Raised By Another,230,3,19,,2590459,223623810,1.99\n2886,Stranger In a Strange Land,229,3,21,,2636428,505056021,1.99\n2887,The 23rd Psalm,231,3,19,,2610416,487401604,1.99\n2888,All the Best Cowboys Have Daddy Issues,230,3,19,,2555492,211743651,1.99\n2889,The Hunting Party,231,3,21,,2611333,520350364,1.99\n2890,Tricia Tanaka Is Dead,229,3,21,,2635010,548197162,1.99\n2891,Enter 77,229,3,21,,2629796,517521422,1.99\n2892,Fire + Water,231,3,21,,2600333,488458695,1.99\n2893,Whatever the Case May Be,230,3,19,,2616410,183867185,1.99\n2894,Hearts and Minds,230,3,19,,2619462,207607466,1.99\n2895,Par Avion,229,3,21,,2629879,517079642,1.99\n2896,The Long Con,231,3,19,,2679583,518376636,1.99\n2897,One of Them,231,3,21,,2698791,542332389,1.99\n2898,Special,230,3,19,,2618530,219961967,1.99\n2899,The Man from Tallahassee,229,3,21,,2637637,550893556,1.99\n2900,Exposé,229,3,21,,2593760,511338017,1.99\n2901,Homecoming,230,3,19,,2515882,210675221,1.99\n2902,Maternity Leave,231,3,21,,2780416,555244214,1.99\n2903,Left Behind,229,3,21,,2635343,538491964,1.99\n2904,Outlaws,230,3,19,,2619887,206500939,1.99\n2905,The Whole Truth,231,3,21,,2610125,495487014,1.99\n2906,...In Translation,230,3,19,,2604575,215441983,1.99\n2907,Lockdown,231,3,21,,2610250,543886056,1.99\n2908,One of Us,229,3,21,,2638096,502387276,1.99\n2909,Catch-22,229,3,21,,2561394,489773399,1.99\n2910,Dave,231,3,19,,2825166,574325829,1.99\n2911,Numbers,230,3,19,,2609772,214709143,1.99\n2912,D.O.C.,229,3,21,,2616032,518556641,1.99\n2913,Deus Ex Machina,230,3,19,,2582009,214996732,1.99\n2914,S.O.S.,231,3,19,,2639541,517979269,1.99\n2915,Do No Harm,230,3,19,,2618487,212039309,1.99\n2916,Two for the Road,231,3,21,,2610958,502404558,1.99\n2917,The Greater Good,230,3,19,,2617784,214130273,1.99\n2918,\"\"\"?\"\"\",231,3,19,,2782333,528227089,1.99\n2919,Born to Run,230,3,19,,2618619,213772057,1.99\n2920,Three Minutes,231,3,19,,2763666,531556853,1.99\n2921,Exodus (Part 1),230,3,19,,2620747,213107744,1.99\n2922,\"Live Together, Die Alone, Pt. 1\",231,3,21,,2478041,457364940,1.99\n2923,Exodus (Part 2) [Season Finale],230,3,19,,2605557,208667059,1.99\n2924,\"Live Together, Die Alone, Pt. 2\",231,3,19,,2656531,503619265,1.99\n2925,Exodus (Part 3) [Season Finale],230,3,19,,2619869,197937785,1.99\n2926,Zoo Station,232,1,1,U2,276349,9056902,0.99\n2927,Even Better Than The Real Thing,232,1,1,U2,221361,7279392,0.99\n2928,One,232,1,1,U2,276192,9158892,0.99\n2929,Until The End Of The World,232,1,1,U2,278700,9132485,0.99\n2930,Who's Gonna Ride Your Wild Horses,232,1,1,U2,316551,10304369,0.99\n2931,So Cruel,232,1,1,U2,349492,11527614,0.99\n2932,The Fly,232,1,1,U2,268982,8825399,0.99\n2933,Mysterious Ways,232,1,1,U2,243826,8062057,0.99\n2934,Tryin' To Throw Your Arms Around The World,232,1,1,U2,232463,7612124,0.99\n2935,Ultraviolet (Light My Way),232,1,1,U2,330788,10754631,0.99\n2936,Acrobat,232,1,1,U2,270288,8824723,0.99\n2937,Love Is Blindness,232,1,1,U2,263497,8531766,0.99\n2938,Beautiful Day,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",248163,8056723,0.99\n2939,Stuck In A Moment You Can't Get Out Of,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",272378,8997366,0.99\n2940,Elevation,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",227552,7479414,0.99\n2941,Walk On,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",296280,9800861,0.99\n2942,Kite,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",266893,8765761,0.99\n2943,In A Little While,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",219271,7189647,0.99\n2944,Wild Honey,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",226768,7466069,0.99\n2945,Peace On Earth,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",288496,9476171,0.99\n2946,When I Look At The World,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",257776,8500491,0.99\n2947,New York,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",330370,10862323,0.99\n2948,Grace,233,1,1,\"Adam Clayton, Bono, Larry Mullen, The Edge\",330657,10877148,0.99\n2949,The Three Sunrises,234,1,1,U2,234788,7717990,0.99\n2950,Spanish Eyes,234,1,1,U2,196702,6392710,0.99\n2951,Sweetest Thing,234,1,1,U2,185103,6154896,0.99\n2952,Love Comes Tumbling,234,1,1,U2,282671,9328802,0.99\n2953,Bass Trap,234,1,1,U2,213289,6834107,0.99\n2954,Dancing Barefoot,234,1,1,Ivan Kral/Patti Smith,287895,9488294,0.99\n2955,Everlasting Love,234,1,1,Buzz Cason/Mac Gayden,202631,6708932,0.99\n2956,Unchained Melody,234,1,1,Alex North/Hy Zaret,294164,9597568,0.99\n2957,Walk To The Water,234,1,1,U2,289253,9523336,0.99\n2958,Luminous Times (Hold On To Love),234,1,1,Brian Eno/U2,277760,9015513,0.99\n2959,Hallelujah Here She Comes,234,1,1,U2,242364,8027028,0.99\n2960,Silver And Gold,234,1,1,Bono,279875,9199746,0.99\n2961,Endless Deep,234,1,1,U2,179879,5899070,0.99\n2962,A Room At The Heartbreak Hotel,234,1,1,U2,274546,9015416,0.99\n2963,\"Trash, Trampoline And The Party Girl\",234,1,1,U2,153965,5083523,0.99\n2964,Vertigo,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",194612,6329502,0.99\n2965,Miracle Drug,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",239124,7760916,0.99\n2966,Sometimes You Can't Make It On Your Own,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",308976,10112863,0.99\n2967,Love And Peace Or Else,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",290690,9476723,0.99\n2968,City Of Blinding Lights,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",347951,11432026,0.99\n2969,All Because Of You,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",219141,7198014,0.99\n2970,A Man And A Woman,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",270132,8938285,0.99\n2971,Crumbs From Your Table,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",303568,9892349,0.99\n2972,One Step Closer,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",231680,7512912,0.99\n2973,Original Of The Species,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",281443,9230041,0.99\n2974,Yahweh,235,1,1,\"Adam Clayton, Bono, Larry Mullen & The Edge\",262034,8636998,0.99\n2975,Discotheque,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",319582,10442206,0.99\n2976,Do You Feel Loved,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",307539,10122694,0.99\n2977,Mofo,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",349178,11583042,0.99\n2978,If God Will Send His Angels,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",322533,10563329,0.99\n2979,Staring At The Sun,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",276924,9082838,0.99\n2980,Last Night On Earth,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",285753,9401017,0.99\n2981,Gone,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",266866,8746301,0.99\n2982,Miami,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",293041,9741603,0.99\n2983,The Playboy Mansion,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",280555,9274144,0.99\n2984,If You Wear That Velvet Dress,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",315167,10227333,0.99\n2985,Please,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",302602,9909484,0.99\n2986,Wake Up Dead Man,236,1,1,\"Bono, The Edge, Adam Clayton, and Larry Mullen\",292832,9515903,0.99\n2987,Helter Skelter,237,1,1,\"Lennon, John/McCartney, Paul\",187350,6097636,0.99\n2988,Van Diemen's Land,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",186044,5990280,0.99\n2989,Desire,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",179226,5874535,0.99\n2990,Hawkmoon 269,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",382458,12494987,0.99\n2991,All Along The Watchtower,237,1,1,\"Dylan, Bob\",264568,8623572,0.99\n2992,I Still Haven't Found What I'm Looking for,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",353567,11542247,0.99\n2993,Freedom For My People,237,1,1,\"Mabins, Macie/Magee, Sterling/Robinson, Bobby\",38164,1249764,0.99\n2994,Silver And Gold,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",349831,11450194,0.99\n2995,Pride (In The Name Of Love),237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",267807,8806361,0.99\n2996,Angel Of Harlem,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",229276,7498022,0.99\n2997,Love Rescue Me,237,1,1,\"Bono/Clayton, Adam/Dylan, Bob/Mullen Jr., Larry/The Edge\",384522,12508716,0.99\n2998,When Love Comes To Town,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",255869,8340954,0.99\n2999,Heartland,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",303360,9867748,0.99\n3000,God Part II,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",195604,6497570,0.99\n3001,The Star Spangled Banner,237,1,1,\"Hendrix, Jimi\",43232,1385810,0.99\n3002,Bullet The Blue Sky,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",337005,10993607,0.99\n3003,All I Want Is You,237,1,1,\"Bono/Clayton, Adam/Mullen Jr., Larry/The Edge\",390243,12729820,0.99\n3004,Pride (In The Name Of Love),238,1,1,U2,230243,7549085,0.99\n3005,New Year's Day,238,1,1,U2,258925,8491818,0.99\n3006,With Or Without You,238,1,1,U2,299023,9765188,0.99\n3007,I Still Haven't Found What I'm Looking For,238,1,1,U2,280764,9306737,0.99\n3008,Sunday Bloody Sunday,238,1,1,U2,282174,9269668,0.99\n3009,Bad,238,1,1,U2,351817,11628058,0.99\n3010,Where The Streets Have No Name,238,1,1,U2,276218,9042305,0.99\n3011,I Will Follow,238,1,1,U2,218253,7184825,0.99\n3012,The Unforgettable Fire,238,1,1,U2,295183,9684664,0.99\n3013,Sweetest Thing,238,1,1,U2 & Daragh O'Toole,183066,6071385,0.99\n3014,Desire,238,1,1,U2,179853,5893206,0.99\n3015,When Love Comes To Town,238,1,1,U2,258194,8479525,0.99\n3016,Angel Of Harlem,238,1,1,U2,230217,7527339,0.99\n3017,All I Want Is You,238,1,1,U2 & Van Dyke Parks,591986,19202252,0.99\n3018,Sunday Bloody Sunday,239,1,1,U2,278204,9140849,0.99\n3019,Seconds,239,1,1,U2,191582,6352121,0.99\n3020,New Year's Day,239,1,1,U2,336274,11054732,0.99\n3021,Like A Song...,239,1,1,U2,287294,9365379,0.99\n3022,Drowning Man,239,1,1,U2,254458,8457066,0.99\n3023,The Refugee,239,1,1,U2,221283,7374043,0.99\n3024,Two Hearts Beat As One,239,1,1,U2,243487,7998323,0.99\n3025,Red Light,239,1,1,U2,225854,7453704,0.99\n3026,Surrender,239,1,1,U2,333505,11221406,0.99\n3027,\"\"\"40\"\"\",239,1,1,U2,157962,5251767,0.99\n3028,Zooropa,240,1,1,U2; Bono,392359,12807979,0.99\n3029,Babyface,240,1,1,U2; Bono,241998,7942573,0.99\n3030,Numb,240,1,1,\"U2; Edge, The\",260284,8577861,0.99\n3031,Lemon,240,1,1,U2; Bono,418324,13988878,0.99\n3032,\"Stay (Faraway, So Close!)\",240,1,1,U2; Bono,298475,9785480,0.99\n3033,Daddy's Gonna Pay For Your Crashed Car,240,1,1,U2; Bono,320287,10609581,0.99\n3034,Some Days Are Better Than Others,240,1,1,U2; Bono,257436,8417690,0.99\n3035,The First Time,240,1,1,U2; Bono,225697,7247651,0.99\n3036,Dirty Day,240,1,1,\"U2; Bono & Edge, The\",324440,10652877,0.99\n3037,The Wanderer,240,1,1,U2; Bono,283951,9258717,0.99\n3038,Breakfast In Bed,241,1,8,,196179,6513325,0.99\n3039,Where Did I Go Wrong,241,1,8,,226742,7485054,0.99\n3040,I Would Do For You,241,1,8,,334524,11193602,0.99\n3041,Homely Girl,241,1,8,,203833,6790788,0.99\n3042,Here I Am (Come And Take Me),241,1,8,,242102,8106249,0.99\n3043,Kingston Town,241,1,8,,226951,7638236,0.99\n3044,Wear You To The Ball,241,1,8,,213342,7159527,0.99\n3045,(I Can't Help) Falling In Love With You,241,1,8,,207568,6905623,0.99\n3046,Higher Ground,241,1,8,,260179,8665244,0.99\n3047,Bring Me Your Cup,241,1,8,,341498,11346114,0.99\n3048,C'est La Vie,241,1,8,,270053,9031661,0.99\n3049,Reggae Music,241,1,8,,245106,8203931,0.99\n3050,Superstition,241,1,8,,319582,10728099,0.99\n3051,Until My Dying Day,241,1,8,,235807,7886195,0.99\n3052,Where Have All The Good Times Gone?,242,1,1,Ray Davies,186723,6063937,0.99\n3053,Hang 'Em High,242,1,1,Alex Van Halen/David Lee Roth/Edward Van Halen/Michael Anthony,210259,6872314,0.99\n3054,Cathedral,242,1,1,Alex Van Halen/David Lee Roth/Edward Van Halen/Michael Anthony,82860,2650998,0.99\n3055,Secrets,242,1,1,Alex Van Halen/David Lee Roth/Edward Van Halen/Michael Anthony,206968,6803255,0.99\n3056,Intruder,242,1,1,Alex Van Halen/David Lee Roth/Edward Van Halen/Michael Anthony,100153,3282142,0.99\n3057,(Oh) Pretty Woman,242,1,1,Bill Dees/Roy Orbison,174680,5665828,0.99\n3058,Dancing In The Street,242,1,1,Ivy Jo Hunter/Marvin Gaye/William Stevenson,225985,7461499,0.99\n3059,Little Guitars (Intro),242,1,1,Alex Van Halen/David Lee Roth/Edward Van Halen/Michael Anthony,42240,1439530,0.99\n3060,Little Guitars,242,1,1,Alex Van Halen/David Lee Roth/Edward Van Halen/Michael Anthony,228806,7453043,0.99\n3061,Big Bad Bill (Is Sweet William Now),242,1,1,Jack Yellen/Milton Ager,165146,5489609,0.99\n3062,The Full Bug,242,1,1,Alex Van Halen/David Lee Roth/Edward Van Halen/Michael Anthony,201116,6551013,0.99\n3063,Happy Trails,242,1,1,Dale Evans,65488,2111141,0.99\n3064,Eruption,243,1,1,\"Edward Van Halen, Alex Van Halen, David Lee Roth, Michael Anthony\",102164,3272891,0.99\n3065,Ain't Talkin' 'bout Love,243,1,1,\"Edward Van Halen, Alex Van Halen, David Lee Roth, Michael Anthony\",228336,7569506,0.99\n3066,Runnin' With The Devil,243,1,1,\"Edward Van Halen, Alex Van Halen, David Lee Roth, Michael Anthony\",215902,7061901,0.99\n3067,Dance the Night Away,243,1,1,\"Edward Van Halen, Alex Van Halen, David Lee Roth, Michael Anthony\",185965,6087433,0.99\n3068,And the Cradle Will Rock...,243,1,1,\"Edward Van Halen, Alex Van Halen, David Lee Roth, Michael Anthony\",213968,7011402,0.99\n3069,Unchained,243,1,1,\"Edward Van Halen, Alex Van Halen, David Lee Roth, Michael Anthony\",208953,6777078,0.99\n3070,Jump,243,1,1,\"Edward Van Halen, Alex Van Halen, David Lee Roth\",241711,7911090,0.99\n3071,Panama,243,1,1,\"Edward Van Halen, Alex Van Halen, David Lee Roth\",211853,6921784,0.99\n3072,Why Can't This Be Love,243,1,1,Van Halen,227761,7457655,0.99\n3073,Dreams,243,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony,/Edward Van Halen, Alex Van Halen, Michael Anthony, Sammy Hagar\",291813,9504119,0.99\n3074,When It's Love,243,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony,/Edward Van Halen, Alex Van Halen, Michael Anthony, Sammy Hagar\",338991,11049966,0.99\n3075,Poundcake,243,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony,/Edward Van Halen, Alex Van Halen, Michael Anthony, Sammy Hagar\",321854,10366978,0.99\n3076,Right Now,243,1,1,Van Halen,321828,10503352,0.99\n3077,Can't Stop Loving You,243,1,1,Van Halen,248502,8107896,0.99\n3078,Humans Being,243,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony,/Edward Van Halen, Alex Van Halen, Michael Anthony, Sammy Hagar\",308950,10014683,0.99\n3079,Can't Get This Stuff No More,243,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony,/Edward Van Halen, Alex Van Halen, Michael Anthony, David Lee Roth\",315376,10355753,0.99\n3080,Me Wise Magic,243,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony,/Edward Van Halen, Alex Van Halen, Michael Anthony, David Lee Roth\",366053,12013467,0.99\n3081,Runnin' With The Devil,244,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony and David Lee Roth\",216032,7056863,0.99\n3082,Eruption,244,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony and David Lee Roth\",102556,3286026,0.99\n3083,You Really Got Me,244,1,1,Ray Davies,158589,5194092,0.99\n3084,Ain't Talkin' 'Bout Love,244,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony and David Lee Roth\",230060,7617284,0.99\n3085,I'm The One,244,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony and David Lee Roth\",226507,7373922,0.99\n3086,Jamie's Cryin',244,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony and David Lee Roth\",210546,6946086,0.99\n3087,Atomic Punk,244,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony and David Lee Roth\",182073,5908861,0.99\n3088,Feel Your Love Tonight,244,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony and David Lee Roth\",222850,7293608,0.99\n3089,Little Dreamer,244,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony and David Lee Roth\",203258,6648122,0.99\n3090,Ice Cream Man,244,1,1,John Brim,200306,6573145,0.99\n3091,On Fire,244,1,1,\"Edward Van Halen, Alex Van Halen, Michael Anthony and David Lee Roth\",180636,5879235,0.99\n3092,Neworld,245,1,1,Van Halen,105639,3495897,0.99\n3093,Without You,245,1,1,Van Halen,390295,12619558,0.99\n3094,One I Want,245,1,1,Van Halen,330788,10743970,0.99\n3095,From Afar,245,1,1,Van Halen,324414,10524554,0.99\n3096,Dirty Water Dog,245,1,1,Van Halen,327392,10709202,0.99\n3097,Once,245,1,1,Van Halen,462837,15378082,0.99\n3098,Fire in the Hole,245,1,1,Van Halen,331728,10846768,0.99\n3099,Josephina,245,1,1,Van Halen,342491,11161521,0.99\n3100,Year to the Day,245,1,1,Van Halen,514612,16621333,0.99\n3101,Primary,245,1,1,Van Halen,86987,2812555,0.99\n3102,Ballot or the Bullet,245,1,1,Van Halen,342282,11212955,0.99\n3103,How Many Say I,245,1,1,Van Halen,363937,11716855,0.99\n3104,Sucker Train Blues,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",267859,8738780,0.99\n3105,Do It For The Kids,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",235911,7693331,0.99\n3106,Big Machine,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",265613,8673442,0.99\n3107,Illegal I Song,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",257750,8483347,0.99\n3108,Spectacle,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",221701,7252876,0.99\n3109,Fall To Pieces,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",270889,8823096,0.99\n3110,Headspace,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",223033,7237986,0.99\n3111,Superhuman,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",255921,8365328,0.99\n3112,Set Me Free,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",247954,8053388,0.99\n3113,You Got No Right,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",335412,10991094,0.99\n3114,Slither,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",248398,8118785,0.99\n3115,Dirty Little Thing,246,1,1,\"Dave Kushner, Duff, Keith Nelson, Matt Sorum, Scott Weiland & Slash\",237844,7732982,0.99\n3116,Loving The Alien,246,1,1,\"Dave Kushner, Duff, Matt Sorum, Scott Weiland & Slash\",348786,11412762,0.99\n3117,Pela Luz Dos Olhos Teus,247,1,7,,119196,3905715,0.99\n3118,A Bencao E Outros,247,1,7,,421093,14234427,0.99\n3119,Tudo Na Mais Santa Paz,247,1,7,,222406,7426757,0.99\n3120,O Velho E Aflor,247,1,7,,275121,9126828,0.99\n3121,Cotidiano N 2,247,1,7,,55902,1805797,0.99\n3122,Adeus,247,1,7,,221884,7259351,0.99\n3123,Samba Pra Endrigo,247,1,7,,259265,8823551,0.99\n3124,So Por Amor,247,1,7,,236591,7745764,0.99\n3125,Meu Pranto Rolou,247,1,7,,181760,6003345,0.99\n3126,Mulher Carioca,247,1,7,,191686,6395048,0.99\n3127,Um Homem Chamado Alfredo,247,1,7,,151640,4976227,0.99\n3128,Samba Do Jato,247,1,7,,220813,7357840,0.99\n3129,\"Oi, La\",247,1,7,,167053,5562700,0.99\n3130,\"Vinicius, Poeta Do Encontro\",247,1,7,,336431,10858776,0.99\n3131,Soneto Da Separacao,247,1,7,,193880,6277511,0.99\n3132,Still Of The Night,141,1,3,Sykes,398210,13043817,0.99\n3133,Here I Go Again,141,1,3,Marsden,233874,7652473,0.99\n3134,Is This Love,141,1,3,Sykes,283924,9262360,0.99\n3135,Love Ain't No Stranger,141,1,3,Galley,259395,8490428,0.99\n3136,Looking For Love,141,1,3,Sykes,391941,12769847,0.99\n3137,Now You're Gone,141,1,3,Vandenberg,251141,8162193,0.99\n3138,Slide It In,141,1,3,Coverdale,202475,6615152,0.99\n3139,Slow An' Easy,141,1,3,Moody,367255,11961332,0.99\n3140,Judgement Day,141,1,3,Vandenberg,317074,10326997,0.99\n3141,You're Gonna Break My Hart Again,141,1,3,Sykes,250853,8176847,0.99\n3142,The Deeper The Love,141,1,3,Vandenberg,262791,8606504,0.99\n3143,Crying In The Rain,141,1,3,Coverdale,337005,10931921,0.99\n3144,Fool For Your Loving,141,1,3,Marsden/Moody,250801,8129820,0.99\n3145,Sweet Lady Luck,141,1,3,Vandenberg,273737,8919163,0.99\n3146,Faixa Amarela,248,1,7,Beto Gogo/Jessé Pai/Luiz Carlos/Zeca Pagodinho,240692,8082036,0.99\n3147,Posso Até Me Apaixonar,248,1,7,Dudu Nobre,200698,6735526,0.99\n3148,Não Sou Mais Disso,248,1,7,Jorge Aragão/Zeca Pagodinho,225985,7613817,0.99\n3149,Vivo Isolado Do Mundo,248,1,7,Alcides Dias Lopes,180035,6073995,0.99\n3150,Coração Em Desalinho,248,1,7,Mauro Diniz/Ratino Sigem,185208,6225948,0.99\n3151,Seu Balancê,248,1,7,Paulinho Rezende/Toninho Geraes,219454,7311219,0.99\n3152,Vai Adiar,248,1,7,Alcino Corrêa/Monarco,270393,9134882,0.99\n3153,Rugas,248,1,7,Augusto Garcez/Nelson Cavaquinho,140930,4703182,0.99\n3154,Feirinha da Pavuna/Luz do Repente/Bagaço da Laranja,248,1,7,\"Arlindo Cruz/Franco/Marquinhos PQD/Negro, Jovelina Pérolo/Zeca Pagodinho\",107206,3593684,0.99\n3155,Sem Essa de Malandro Agulha,248,1,7,Aldir Blanc/Jayme Vignoli,158484,5332668,0.99\n3156,Chico Não Vai na Corimba,248,1,7,Dudu Nobre/Zeca Pagodinho,269374,9122188,0.99\n3157,Papel Principal,248,1,7,Almir Guineto/Dedé Paraiso/Luverci Ernesto,217495,7325302,0.99\n3158,Saudade Louca,248,1,7,Acyr Marques/Arlindo Cruz/Franco,243591,8136475,0.99\n3159,Camarão que Dorme e Onda Leva,248,1,7,\"Acyi Marques/Arlindo Bruz/Braço, Beto Sem/Zeca Pagodinho\",299102,10012231,0.99\n3160,Sapopemba e Maxambomba,248,1,7,Nei Lopes/Wilson Moreira,245394,8268712,0.99\n3161,Minha Fé,248,1,7,Murilão,206994,6981474,0.99\n3162,Lua de Ogum,248,1,7,Ratinho/Zeca Pagodinho,168463,5719129,0.99\n3163,Samba pras moças,248,1,7,Grazielle/Roque Ferreira,152816,5121366,0.99\n3164,Verdade,248,1,7,Carlinhos Santana/Nelson Rufino,332826,11120708,0.99\n3165,The Brig,229,3,21,,2617325,488919543,1.99\n3166,.07%,228,3,21,,2585794,541715199,1.99\n3167,Five Years Gone,228,3,21,,2587712,530551890,1.99\n3168,The Hard Part,228,3,21,,2601017,475996611,1.99\n3169,The Man Behind the Curtain,229,3,21,,2615990,493951081,1.99\n3170,Greatest Hits,229,3,21,,2617117,522102916,1.99\n3171,Landslide,228,3,21,,2600725,518677861,1.99\n3172,The Office: An American Workplace (Pilot),249,3,19,,1380833,290482361,1.99\n3173,Diversity Day,249,3,19,,1306416,257879716,1.99\n3174,Health Care,249,3,19,,1321791,260493577,1.99\n3175,The Alliance,249,3,19,,1317125,266203162,1.99\n3176,Basketball,249,3,19,,1323541,267464180,1.99\n3177,Hot Girl,249,3,19,,1325458,267836576,1.99\n3178,The Dundies,250,3,19,,1253541,246845576,1.99\n3179,Sexual Harassment,250,3,19,,1294541,273069146,1.99\n3180,Office Olympics,250,3,19,,1290458,256247623,1.99\n3181,The Fire,250,3,19,,1288166,266856017,1.99\n3182,Halloween,250,3,19,,1315333,249205209,1.99\n3183,The Fight,250,3,19,,1320028,277149457,1.99\n3184,The Client,250,3,19,,1299341,253836788,1.99\n3185,Performance Review,250,3,19,,1292458,256143822,1.99\n3186,Email Surveillance,250,3,19,,1328870,265101113,1.99\n3187,Christmas Party,250,3,19,,1282115,260891300,1.99\n3188,Booze Cruise,250,3,19,,1267958,252518021,1.99\n3189,The Injury,250,3,19,,1275275,253912762,1.99\n3190,The Secret,250,3,19,,1264875,253143200,1.99\n3191,The Carpet,250,3,19,,1264375,256477011,1.99\n3192,Boys and Girls,250,3,19,,1278333,255245729,1.99\n3193,Valentine's Day,250,3,19,,1270375,253552710,1.99\n3194,Dwight's Speech,250,3,19,,1278041,255001728,1.99\n3195,Take Your Daughter to Work Day,250,3,19,,1268333,253451012,1.99\n3196,Michael's Birthday,250,3,19,,1237791,247238398,1.99\n3197,Drug Testing,250,3,19,,1278625,244626927,1.99\n3198,Conflict Resolution,250,3,19,,1274583,253808658,1.99\n3199,Casino Night - Season Finale,250,3,19,,1712791,327642458,1.99\n3200,Gay Witch Hunt,251,3,19,,1326534,276942637,1.99\n3201,The Convention,251,3,19,,1297213,255117055,1.99\n3202,The Coup,251,3,19,,1276526,267205501,1.99\n3203,Grief Counseling,251,3,19,,1282615,256912833,1.99\n3204,The Initiation,251,3,19,,1280113,251728257,1.99\n3205,Diwali,251,3,19,,1279904,252726644,1.99\n3206,Branch Closing,251,3,19,,1822781,358761786,1.99\n3207,The Merger,251,3,19,,1801926,345960631,1.99\n3208,The Convict,251,3,22,,1273064,248863427,1.99\n3209,\"A Benihana Christmas, Pts. 1 & 2\",251,3,22,,2519436,515301752,1.99\n3210,Back from Vacation,251,3,22,,1271688,245378749,1.99\n3211,Traveling Salesmen,251,3,22,,1289039,250822697,1.99\n3212,Producer's Cut: The Return,251,3,22,,1700241,337219980,1.99\n3213,Ben Franklin,251,3,22,,1271938,264168080,1.99\n3214,Phyllis's Wedding,251,3,22,,1271521,258561054,1.99\n3215,Business School,251,3,22,,1302093,254402605,1.99\n3216,Cocktails,251,3,22,,1272522,259011909,1.99\n3217,The Negotiation,251,3,22,,1767851,371663719,1.99\n3218,Safety Training,251,3,22,,1271229,253054534,1.99\n3219,Product Recall,251,3,22,,1268268,251208610,1.99\n3220,Women's Appreciation,251,3,22,,1732649,338778844,1.99\n3221,Beach Games,251,3,22,,1676134,333671149,1.99\n3222,The Job,251,3,22,,2541875,501060138,1.99\n3223,How to Stop an Exploding Man,228,3,21,,2687103,487881159,1.99\n3224,Through a Looking Glass,229,3,21,,5088838,1059546140,1.99\n3225,Your Time Is Gonna Come,252,2,1,\"Page, Jones\",310774,5126563,0.99\n3226,\"Battlestar Galactica, Pt. 1\",253,3,20,,2952702,541359437,1.99\n3227,\"Battlestar Galactica, Pt. 2\",253,3,20,,2956081,521387924,1.99\n3228,\"Battlestar Galactica, Pt. 3\",253,3,20,,2927802,554509033,1.99\n3229,\"Lost Planet of the Gods, Pt. 1\",253,3,20,,2922547,537812711,1.99\n3230,\"Lost Planet of the Gods, Pt. 2\",253,3,20,,2914664,534343985,1.99\n3231,The Lost Warrior,253,3,20,,2920045,558872190,1.99\n3232,The Long Patrol,253,3,20,,2925008,513122217,1.99\n3233,\"The Gun On Ice Planet Zero, Pt. 1\",253,3,20,,2907615,540980196,1.99\n3234,\"The Gun On Ice Planet Zero, Pt. 2\",253,3,20,,2924341,546542281,1.99\n3235,The Magnificent Warriors,253,3,20,,2924716,570152232,1.99\n3236,The Young Lords,253,3,20,,2863571,587051735,1.99\n3237,\"The Living Legend, Pt. 1\",253,3,20,,2924507,503641007,1.99\n3238,\"The Living Legend, Pt. 2\",253,3,20,,2923298,515632754,1.99\n3239,Fire In Space,253,3,20,,2926593,536784757,1.99\n3240,\"War of the Gods, Pt. 1\",253,3,20,,2922630,505761343,1.99\n3241,\"War of the Gods, Pt. 2\",253,3,20,,2923381,487899692,1.99\n3242,The Man With Nine Lives,253,3,20,,2956998,577829804,1.99\n3243,Murder On the Rising Star,253,3,20,,2935894,551759986,1.99\n3244,\"Greetings from Earth, Pt. 1\",253,3,20,,2960293,536824558,1.99\n3245,\"Greetings from Earth, Pt. 2\",253,3,20,,2903778,527842860,1.99\n3246,Baltar's Escape,253,3,20,,2922088,525564224,1.99\n3247,Experiment In Terra,253,3,20,,2923548,547982556,1.99\n3248,Take the Celestra,253,3,20,,2927677,512381289,1.99\n3249,The Hand of God,253,3,20,,2924007,536583079,1.99\n3250,Pilot,254,3,19,,2484567,492670102,1.99\n3251,\"Through the Looking Glass, Pt. 2\",229,3,21,,2617117,550943353,1.99\n3252,\"Through the Looking Glass, Pt. 1\",229,3,21,,2610860,493211809,1.99\n3253,Instant Karma,255,2,9,,193188,3150090,0.99\n3254,#9 Dream,255,2,9,,278312,4506425,0.99\n3255,Mother,255,2,9,,287740,4656660,0.99\n3256,Give Peace a Chance,255,2,9,,274644,4448025,0.99\n3257,Cold Turkey,255,2,9,,281424,4556003,0.99\n3258,Whatever Gets You Thru the Night,255,2,9,,215084,3499018,0.99\n3259,I'm Losing You,255,2,9,,240719,3907467,0.99\n3260,Gimme Some Truth,255,2,9,,232778,3780807,0.99\n3261,\"Oh, My Love\",255,2,9,,159473,2612788,0.99\n3262,Imagine,255,2,9,,192329,3136271,0.99\n3263,Nobody Told Me,255,2,9,,210348,3423395,0.99\n3264,Jealous Guy,255,2,9,,239094,3881620,0.99\n3265,Working Class Hero,255,2,9,,265449,4301430,0.99\n3266,Power to the People,255,2,9,,213018,3466029,0.99\n3267,Imagine,255,2,9,,219078,3562542,0.99\n3268,Beautiful Boy,255,2,9,,227995,3704642,0.99\n3269,Isolation,255,2,9,,156059,2558399,0.99\n3270,Watching the Wheels,255,2,9,,198645,3237063,0.99\n3271,Grow Old With Me,255,2,9,,149093,2447453,0.99\n3272,Gimme Some Truth,255,2,9,,187546,3060083,0.99\n3273,[Just Like] Starting Over,255,2,9,,215549,3506308,0.99\n3274,God,255,2,9,,260410,4221135,0.99\n3275,Real Love,255,2,9,,236911,3846658,0.99\n3276,Sympton of the Universe,256,2,1,,340890,5489313,0.99\n3277,Snowblind,256,2,1,,295960,4773171,0.99\n3278,Black Sabbath,256,2,1,,364180,5860455,0.99\n3279,Fairies Wear Boots,256,2,1,,392764,6315916,0.99\n3280,War Pigs,256,2,1,,515435,8270194,0.99\n3281,The Wizard,256,2,1,,282678,4561796,0.99\n3282,N.I.B.,256,2,1,,335248,5399456,0.99\n3283,Sweet Leaf,256,2,1,,354706,5709700,0.99\n3284,Never Say Die,256,2,1,,258343,4173799,0.99\n3285,\"Sabbath, Bloody Sabbath\",256,2,1,,333622,5373633,0.99\n3286,Iron Man/Children of the Grave,256,2,1,,552308,8858616,0.99\n3287,Paranoid,256,2,1,,189171,3071042,0.99\n3288,Rock You Like a Hurricane,257,2,1,,255766,4300973,0.99\n3289,No One Like You,257,2,1,,240325,4050259,0.99\n3290,The Zoo,257,2,1,,332740,5550779,0.99\n3291,Loving You Sunday Morning,257,2,1,,339125,5654493,0.99\n3292,Still Loving You,257,2,1,,390674,6491444,0.99\n3293,Big City Nights,257,2,1,,251865,4237651,0.99\n3294,Believe in Love,257,2,1,,325774,5437651,0.99\n3295,Rhythm of Love,257,2,1,,231246,3902834,0.99\n3296,I Can't Explain,257,2,1,,205332,3482099,0.99\n3297,Tease Me Please Me,257,2,1,,287229,4811894,0.99\n3298,Wind of Change,257,2,1,,315325,5268002,0.99\n3299,Send Me an Angel,257,2,1,,273041,4581492,0.99\n3300,Jump Around,258,1,17,E. Schrody/L. Muggerud,217835,8715653,0.99\n3301,Salutations,258,1,17,E. Schrody/L. Dimant,69120,2767047,0.99\n3302,Put Your Head Out,258,1,17,E. Schrody/L. Freese/L. Muggerud,182230,7291473,0.99\n3303,Top O' The Morning To Ya,258,1,17,E. Schrody/L. Dimant,216633,8667599,0.99\n3304,Commercial 1,258,1,17,L. Muggerud,7941,319888,0.99\n3305,House And The Rising Sun,258,1,17,E. Schrody/J. Vasquez/L. Dimant,219402,8778369,0.99\n3306,Shamrocks And Shenanigans,258,1,17,E. Schrody/L. Dimant,218331,8735518,0.99\n3307,House Of Pain Anthem,258,1,17,E. Schrody/L. Dimant,155611,6226713,0.99\n3308,\"Danny Boy, Danny Boy\",258,1,17,E. Schrody/L. Muggerud,114520,4583091,0.99\n3309,Guess Who's Back,258,1,17,E. Schrody/L. Muggerud,238393,9537994,0.99\n3310,Commercial 2,258,1,17,L. Muggerud,21211,850698,0.99\n3311,Put On Your Shit Kickers,258,1,17,E. Schrody/L. Muggerud,190432,7619569,0.99\n3312,Come And Get Some Of This,258,1,17,E. Schrody/L. Muggerud/R. Medrano,170475,6821279,0.99\n3313,Life Goes On,258,1,17,E. Schrody/R. Medrano,163030,6523458,0.99\n3314,One For The Road,258,1,17,E. Schrody/L. Dimant/L. Muggerud,170213,6810820,0.99\n3315,Feel It,258,1,17,E. Schrody/R. Medrano,239908,9598588,0.99\n3316,All My Love,258,1,17,E. Schrody/L. Dimant,200620,8027065,0.99\n3317,Jump Around (Pete Rock Remix),258,1,17,E. Schrody/L. Muggerud,236120,9447101,0.99\n3318,Shamrocks And Shenanigans (Boom Shalock Lock Boom/Butch Vig Mix),258,1,17,E. Schrody/L. Dimant,237035,9483705,0.99\n3319,Instinto Colectivo,259,1,15,,300564,12024875,0.99\n3320,Chapa o Coco,259,1,15,,143830,5755478,0.99\n3321,Prostituta,259,1,15,,359000,14362307,0.99\n3322,Eu So Queria Sumir,259,1,15,,269740,10791921,0.99\n3323,Tres Reis,259,1,15,,304143,12168015,0.99\n3324,Um Lugar ao Sol,259,1,15,,212323,8495217,0.99\n3325,Batalha Naval,259,1,15,,285727,11431382,0.99\n3326,Todo o Carnaval tem seu Fim,259,1,15,,237426,9499371,0.99\n3327,O Misterio do Samba,259,1,15,,226142,9047970,0.99\n3328,Armadura,259,1,15,,232881,9317533,0.99\n3329,Na Ladeira,259,1,15,,221570,8865099,0.99\n3330,Carimbo,259,1,15,,328751,13152314,0.99\n3331,Catimbo,259,1,15,,254484,10181692,0.99\n3332,Funk de Bamba,259,1,15,,237322,9495184,0.99\n3333,Chega no Suingue,259,1,15,,221805,8874509,0.99\n3334,Mun-Ra,259,1,15,,274651,10988338,0.99\n3335,Freestyle Love,259,1,15,,318484,12741680,0.99\n3336,War Pigs,260,4,23,,234013,8052374,0.99\n3337,\"Past, Present, and Future\",261,3,21,,2492867,490796184,1.99\n3338,The Beginning of the End,261,3,21,,2611903,526865050,1.99\n3339,LOST Season 4 Trailer,261,3,21,,112712,20831818,1.99\n3340,LOST In 8:15,261,3,21,,497163,98460675,1.99\n3341,Confirmed Dead,261,3,21,,2611986,512168460,1.99\n3342,The Economist,261,3,21,,2609025,516934914,1.99\n3343,Eggtown,261,3,19,,2608817,501061240,1.99\n3344,The Constant,261,3,21,,2611569,520209363,1.99\n3345,The Other Woman,261,3,21,,2605021,513246663,1.99\n3346,Ji Yeon,261,3,19,,2588797,506458858,1.99\n3347,Meet Kevin Johnson,261,3,19,,2612028,504132981,1.99\n3348,The Shape of Things to Come,261,3,21,,2591299,502284266,1.99\n3349,Amanda,262,5,2,Luca Gusella,246503,4011615,0.99\n3350,Despertar,262,5,2,Andrea Dulbecco,307385,4821485,0.99\n3351,Din Din Wo (Little Child),263,5,16,Habib Koité,285837,4615841,0.99\n3352,Distance,264,5,15,Karsh Kale/Vishal Vaid,327122,5327463,0.99\n3353,I Guess You're Right,265,5,1,\"Darius \"\"Take One\"\" Minwalla/Jon Auer/Ken Stringfellow/Matt Harris\",212044,3453849,0.99\n3354,I Ka Barra (Your Work),263,5,16,Habib Koité,300605,4855457,0.99\n3355,Love Comes,265,5,1,\"Darius \"\"Take One\"\" Minwalla/Jon Auer/Ken Stringfellow/Matt Harris\",199923,3240609,0.99\n3356,Muita Bobeira,266,5,7,Luciana Souza,172710,2775071,0.99\n3357,OAM's Blues,267,5,2,Aaron Goldberg,266936,4292028,0.99\n3358,One Step Beyond,264,5,15,Karsh Kale,366085,6034098,0.99\n3359,\"Symphony No. 3 in E-flat major, Op. 55, \"\"Eroica\"\" - Scherzo: Allegro Vivace\",268,5,24,Ludwig van Beethoven,356426,5817216,0.99\n3360,Something Nice Back Home,261,3,21,,2612779,484711353,1.99\n3361,Cabin Fever,261,3,21,,2612028,477733942,1.99\n3362,\"There's No Place Like Home, Pt. 1\",261,3,21,,2609526,522919189,1.99\n3363,\"There's No Place Like Home, Pt. 2\",261,3,21,,2497956,523748920,1.99\n3364,\"There's No Place Like Home, Pt. 3\",261,3,21,,2582957,486161766,1.99\n3365,Say Hello 2 Heaven,269,2,23,,384497,6477217,0.99\n3366,Reach Down,269,2,23,,672773,11157785,0.99\n3367,Hunger Strike,269,2,23,,246292,4233212,0.99\n3368,Pushin Forward Back,269,2,23,,225278,3892066,0.99\n3369,Call Me a Dog,269,2,23,,304458,5177612,0.99\n3370,Times of Trouble,269,2,23,,342539,5795951,0.99\n3371,Wooden Jesus,269,2,23,,250565,4302603,0.99\n3372,Your Savior,269,2,23,,244226,4199626,0.99\n3373,Four Walled World,269,2,23,,414474,6964048,0.99\n3374,All Night Thing,269,2,23,,231803,3997982,0.99\n3375,No Such Thing,270,2,23,Chris Cornell,224837,3691272,0.99\n3376,Poison Eye,270,2,23,Chris Cornell,237120,3890037,0.99\n3377,Arms Around Your Love,270,2,23,Chris Cornell,214016,3516224,0.99\n3378,Safe and Sound,270,2,23,Chris Cornell,256764,4207769,0.99\n3379,She'll Never Be Your Man,270,2,23,Chris Cornell,204078,3355715,0.99\n3380,Ghosts,270,2,23,Chris Cornell,231547,3799745,0.99\n3381,Killing Birds,270,2,23,Chris Cornell,218498,3588776,0.99\n3382,Billie Jean,270,2,23,Michael Jackson,281401,4606408,0.99\n3383,Scar On the Sky,270,2,23,Chris Cornell,220193,3616618,0.99\n3384,Your Soul Today,270,2,23,Chris Cornell,205959,3385722,0.99\n3385,Finally Forever,270,2,23,Chris Cornell,217035,3565098,0.99\n3386,Silence the Voices,270,2,23,Chris Cornell,267376,4379597,0.99\n3387,Disappearing Act,270,2,23,Chris Cornell,273320,4476203,0.99\n3388,You Know My Name,270,2,23,Chris Cornell,240255,3940651,0.99\n3389,Revelations,271,2,23,,252376,4111051,0.99\n3390,One and the Same,271,2,23,,217732,3559040,0.99\n3391,Sound of a Gun,271,2,23,,260154,4234990,0.99\n3392,Until We Fall,271,2,23,,230758,3766605,0.99\n3393,Original Fire,271,2,23,,218916,3577821,0.99\n3394,Broken City,271,2,23,,228366,3728955,0.99\n3395,Somedays,271,2,23,,213831,3497176,0.99\n3396,Shape of Things to Come,271,2,23,,274597,4465399,0.99\n3397,Jewel of the Summertime,271,2,23,,233242,3806103,0.99\n3398,Wide Awake,271,2,23,,266308,4333050,0.99\n3399,Nothing Left to Say But Goodbye,271,2,23,,213041,3484335,0.99\n3400,Moth,271,2,23,,298049,4838884,0.99\n3401,Show Me How to Live (Live at the Quart Festival),271,2,23,,301974,4901540,0.99\n3402,\"Band Members Discuss Tracks from \"\"Revelations\"\"\",271,3,23,,294294,61118891,0.99\n3403,Intoitus: Adorate Deum,272,2,24,Anonymous,245317,4123531,0.99\n3404,\"Miserere mei, Deus\",273,2,24,Gregorio Allegri,501503,8285941,0.99\n3405,Canon and Gigue in D Major: I. Canon,274,2,24,Johann Pachelbel,271788,4438393,0.99\n3406,\"Concerto No. 1 in E Major, RV 269 \"\"Spring\"\": I. Allegro\",275,2,24,Antonio Vivaldi,199086,3347810,0.99\n3407,\"Concerto for 2 Violins in D Minor, BWV 1043: I. Vivace\",276,2,24,Johann Sebastian Bach,193722,3192890,0.99\n3408,\"Aria Mit 30 Veränderungen, BWV 988 \"\"Goldberg Variations\"\": Aria\",277,2,24,Johann Sebastian Bach,120463,2081895,0.99\n3409,\"Suite for Solo Cello No. 1 in G Major, BWV 1007: I. Prélude\",278,2,24,Johann Sebastian Bach,143288,2315495,0.99\n3410,\"The Messiah: Behold, I Tell You a Mystery... The Trumpet Shall Sound\",279,2,24,George Frideric Handel,582029,9553140,0.99\n3411,Solomon HWV 67: The Arrival of the Queen of Sheba,280,2,24,George Frideric Handel,197135,3247914,0.99\n3412,\"\"\"Eine Kleine Nachtmusik\"\" Serenade In G, K. 525: I. Allegro\",281,2,24,Wolfgang Amadeus Mozart,348971,5760129,0.99\n3413,\"Concerto for Clarinet in A Major, K. 622: II. Adagio\",282,2,24,Wolfgang Amadeus Mozart,394482,6474980,0.99\n3414,\"Symphony No. 104 in D Major \"\"London\"\": IV. Finale: Spiritoso\",283,4,24,Franz Joseph Haydn,306687,10085867,0.99\n3415,Symphony No.5 in C Minor: I. Allegro con brio,284,2,24,Ludwig van Beethoven,392462,6419730,0.99\n3416,Ave Maria,285,2,24,Franz Schubert,338243,5605648,0.99\n3417,\"Nabucco: Chorus, \"\"Va, Pensiero, Sull'ali Dorate\"\"\",286,2,24,Giuseppe Verdi,274504,4498583,0.99\n3418,Die Walküre: The Ride of the Valkyries,287,2,24,Richard Wagner,189008,3114209,0.99\n3419,\"Requiem, Op.48: 4. Pie Jesu\",288,2,24,Gabriel Fauré,258924,4314850,0.99\n3420,\"The Nutcracker, Op. 71a, Act II: Scene 14: Pas de deux: Dance of the Prince & the Sugar-Plum Fairy\",289,2,24,Peter Ilyich Tchaikovsky,304226,5184289,0.99\n3421,\"Nimrod (Adagio) from Variations On an Original Theme, Op. 36 \"\"Enigma\"\"\",290,2,24,Edward Elgar,250031,4124707,0.99\n3422,Madama Butterfly: Un Bel Dì Vedremo,291,2,24,Giacomo Puccini,277639,4588197,0.99\n3423,\"Jupiter, the Bringer of Jollity\",292,2,24,Gustav Holst,522099,8547876,0.99\n3424,\"Turandot, Act III, Nessun dorma!\",293,2,24,Giacomo Puccini,176911,2920890,0.99\n3425,\"Adagio for Strings from the String Quartet, Op. 11\",294,2,24,Samuel Barber,596519,9585597,0.99\n3426,Carmina Burana: O Fortuna,295,2,24,Carl Orff,156710,2630293,0.99\n3427,Fanfare for the Common Man,296,2,24,Aaron Copland,198064,3211245,0.99\n3428,Branch Closing,251,3,22,,1814855,360331351,1.99\n3429,The Return,251,3,22,,1705080,343877320,1.99\n3430,\"Toccata and Fugue in D Minor, BWV 565: I. Toccata\",297,2,24,Johann Sebastian Bach,153901,2649938,0.99\n3431,\"Symphony No.1 in D Major, Op.25 \"\"Classical\"\", Allegro Con Brio\",298,2,24,Sergei Prokofiev,254001,4195542,0.99\n3432,\"Scheherazade, Op. 35: I. The Sea and Sindbad's Ship\",299,2,24,Nikolai Rimsky-Korsakov,545203,8916313,0.99\n3433,\"Concerto No.2 in F Major, BWV1047, I. Allegro\",300,2,24,Johann Sebastian Bach,307244,5064553,0.99\n3434,\"Concerto for Piano No. 2 in F Minor, Op. 21: II. Larghetto\",301,2,24,Frédéric Chopin,560342,9160082,0.99\n3435,Cavalleria Rusticana \\ Act \\ Intermezzo Sinfonico,302,2,24,Pietro Mascagni,243436,4001276,0.99\n3436,\"Karelia Suite, Op.11: 2. Ballade (Tempo Di Menuetto)\",303,2,24,Jean Sibelius,406000,5908455,0.99\n3437,\"Piano Sonata No. 14 in C Sharp Minor, Op. 27, No. 2, \"\"Moonlight\"\": I. Adagio sostenuto\",304,2,24,Ludwig van Beethoven,391000,6318740,0.99\n3438,Fantasia On Greensleeves,280,2,24,Ralph Vaughan Williams,268066,4513190,0.99\n3439,\"Das Lied Von Der Erde, Von Der Jugend\",305,2,24,Gustav Mahler,223583,3700206,0.99\n3440,\"Concerto for Cello and Orchestra in E minor, Op. 85: I. Adagio - Moderato\",306,2,24,Edward Elgar,483133,7865479,0.99\n3441,Two Fanfares for Orchestra: II. Short Ride in a Fast Machine,307,2,24,John Adams,254930,4310896,0.99\n3442,\"Wellington's Victory or the Battle Symphony, Op.91: 2. Symphony of Triumph\",308,2,24,Ludwig van Beethoven,412000,6965201,0.99\n3443,Missa Papae Marcelli: Kyrie,309,2,24,Giovanni Pierluigi da Palestrina,240666,4244149,0.99\n3444,Romeo et Juliette: No. 11 - Danse des Chevaliers,310,2,24,,275015,4519239,0.99\n3445,On the Beautiful Blue Danube,311,2,24,Johann Strauss II,526696,8610225,0.99\n3446,\"Symphonie Fantastique, Op. 14: V. Songe d'une nuit du sabbat\",312,2,24,Hector Berlioz,561967,9173344,0.99\n3447,Carmen: Overture,313,2,24,Georges Bizet,132932,2189002,0.99\n3448,\"Lamentations of Jeremiah, First Set \\ Incipit Lamentatio\",314,2,24,Thomas Tallis,69194,1208080,0.99\n3449,\"Music for the Royal Fireworks, HWV351 (1749): La Réjouissance\",315,2,24,George Frideric Handel,120000,2193734,0.99\n3450,\"Peer Gynt Suite No.1, Op.46: 1. Morning Mood\",316,2,24,Edvard Grieg,253422,4298769,0.99\n3451,\"Die Zauberflöte, K.620: \"\"Der Hölle Rache Kocht in Meinem Herze\"\"\",317,2,25,Wolfgang Amadeus Mozart,174813,2861468,0.99\n3452,\"SCRIABIN: Prelude in B Major, Op. 11, No. 11\",318,4,24,,101293,3819535,0.99\n3453,\"Pavan, Lachrimae Antiquae\",319,2,24,John Dowland,253281,4211495,0.99\n3454,\"Symphony No. 41 in C Major, K. 551, \"\"Jupiter\"\": IV. Molto allegro\",320,2,24,Wolfgang Amadeus Mozart,362933,6173269,0.99\n3455,Rehab,321,2,14,,213240,3416878,0.99\n3456,You Know I'm No Good,321,2,14,,256946,4133694,0.99\n3457,Me & Mr. Jones,321,2,14,,151706,2449438,0.99\n3458,Just Friends,321,2,14,,191933,3098906,0.99\n3459,Back to Black,321,2,14,Mark Ronson,240320,3852953,0.99\n3460,Love Is a Losing Game,321,2,14,,154386,2509409,0.99\n3461,Tears Dry On Their Own,321,2,14,Nickolas Ashford & Valerie Simpson,185293,2996598,0.99\n3462,Wake Up Alone,321,2,14,Paul O'duffy,221413,3576773,0.99\n3463,Some Unholy War,321,2,14,,141520,2304465,0.99\n3464,He Can Only Hold Her,321,2,14,Richard Poindexter & Robert Poindexter,166680,2666531,0.99\n3465,You Know I'm No Good (feat. Ghostface Killah),321,2,14,,202320,3260658,0.99\n3466,Rehab (Hot Chip Remix),321,2,14,,418293,6670600,0.99\n3467,Intro / Stronger Than Me,322,2,9,,234200,3832165,0.99\n3468,You Sent Me Flying / Cherry,322,2,9,,409906,6657517,0.99\n3469,F**k Me Pumps,322,2,9,Salaam Remi,200253,3324343,0.99\n3470,I Heard Love Is Blind,322,2,9,,129666,2190831,0.99\n3471,(There Is) No Greater Love (Teo Licks),322,2,9,Isham Jones & Marty Symes,167933,2773507,0.99\n3472,In My Bed,322,2,9,Salaam Remi,315960,5211774,0.99\n3473,Take the Box,322,2,9,Luke Smith,199160,3281526,0.99\n3474,October Song,322,2,9,Matt Rowe & Stefan Skarbek,204846,3358125,0.99\n3475,What Is It About Men,322,2,9,\"Delroy \"\"Chris\"\" Cooper, Donovan Jackson, Earl Chinna Smith, Felix Howard, Gordon Williams, Luke Smith, Paul Watson & Wilburn Squiddley Cole\",209573,3426106,0.99\n3476,Help Yourself,322,2,9,\"Freddy James, Jimmy hogarth & Larry Stock\",300884,5029266,0.99\n3477,Amy Amy Amy (Outro),322,2,9,\"Astor Campbell, Delroy \"\"Chris\"\" Cooper, Donovan Jackson, Dorothy Fields, Earl Chinna Smith, Felix Howard, Gordon Williams, James Moody, Jimmy McHugh, Matt Rowe, Salaam Remi & Stefan Skarbek\",663426,10564704,0.99\n3478,Slowness,323,2,23,,215386,3644793,0.99\n3479,\"Prometheus Overture, Op. 43\",324,4,24,Ludwig van Beethoven,339567,10887931,0.99\n3480,Sonata for Solo Violin: IV: Presto,325,4,24,Béla Bartók,299350,9785346,0.99\n3481,\"A Midsummer Night's Dream, Op.61 Incidental Music: No.7 Notturno\",326,2,24,,387826,6497867,0.99\n3482,\"Suite No. 3 in D, BWV 1068: III. Gavotte I & II\",327,2,24,Johann Sebastian Bach,225933,3847164,0.99\n3483,\"Concert pour 4 Parties de V**les, H. 545: I. Prelude\",328,2,24,Marc-Antoine Charpentier,110266,1973559,0.99\n3484,Adios nonino,329,2,24,Astor Piazzolla,289388,4781384,0.99\n3485,\"Symphony No. 3 Op. 36 for Orchestra and Soprano \"\"Symfonia Piesni Zalosnych\"\" \\ Lento E Largo - Tranquillissimo\",330,2,24,Henryk Górecki,567494,9273123,0.99\n3486,\"Act IV, Symphony\",331,2,24,Henry Purcell,364296,5987695,0.99\n3487,\"3 Gymnopédies: No.1 - Lent Et Grave, No.3 - Lent Et Douloureux\",332,2,24,Erik Satie,385506,6458501,0.99\n3488,\"Music for the Funeral of Queen Mary: VI. \"\"Thou Knowest, Lord, the Secrets of Our Hearts\"\"\",333,2,24,Henry Purcell,142081,2365930,0.99\n3489,Symphony No. 2: III. Allegro vivace,334,2,24,Kurt Weill,376510,6129146,0.99\n3490,\"Partita in E Major, BWV 1006A: I. Prelude\",335,2,24,Johann Sebastian Bach,285673,4744929,0.99\n3491,Le Sacre Du Printemps: I.iv. Spring Rounds,336,2,24,Igor Stravinsky,234746,4072205,0.99\n3492,Sing Joyfully,314,2,24,William Byrd,133768,2256484,0.99\n3493,\"Metopes, Op. 29: Calypso\",337,2,24,Karol Szymanowski,333669,5548755,0.99\n3494,\"Symphony No. 2, Op. 16 -  \"\"The Four Temperaments\"\": II. Allegro Comodo e Flemmatico\",338,2,24,Carl Nielsen,286998,4834785,0.99\n3495,\"24 Caprices, Op. 1, No. 24, for Solo Violin, in A Minor\",339,2,24,Niccolò Paganini,265541,4371533,0.99\n3496,\"Étude 1, In C Major - Preludio (Presto) - Liszt\",340,4,24,,51780,2229617,0.99\n3497,\"Erlkonig, D.328\",341,2,24,,261849,4307907,0.99\n3498,\"Concerto for Violin, Strings and Continuo in G Major, Op. 3, No. 9: I. Allegro\",342,4,24,Pietro Antonio Locatelli,493573,16454937,0.99\n3499,Pini Di Roma (Pinien Von Rom) \\ I Pini Della Via Appia,343,2,24,,286741,4718950,0.99\n3500,\"String Quartet No. 12 in C Minor, D. 703 \"\"Quartettsatz\"\": II. Andante - Allegro assai\",344,2,24,Franz Schubert,139200,2283131,0.99\n3501,\"L'orfeo, Act 3, Sinfonia (Orchestra)\",345,2,24,Claudio Monteverdi,66639,1189062,0.99\n3502,\"Quintet for Horn, Violin, 2 Violas, and Cello in E Flat Major, K. 407/386c: III. Allegro\",346,2,24,Wolfgang Amadeus Mozart,221331,3665114,0.99\n3503,Koyaanisqatsi,347,2,10,Philip Glass,206005,3305164,0.99\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/dbs/README.md",
    "content": "# PRQL test-databases\n\nTest PRQL queries against various SQL RDBMS.\n\n## In-process DBs\n\nTo run tests against DuckDB & SQLite, no additional setup is required; simply\nrun:\n\n```sh\ncargo test --features=test-dbs\n```\n\n## External DBs\n\nTo run tests against external databases — currently Postgres, MySQL, SQL Server,\nClickHouse and GlareDB are tested using `docker compose` to create the\ndatabases.\n\nThe steps are all covered by `task test-rust-external-dbs`; to run them\nmanually:\n\n1. Run `docker compose up` (may take a while on the first time):\n\n   ```sh\n   cd prqlc/prqlc/tests/integration/dbs && docker compose up -d\n   ```\n\n2. Run the tests:\n\n   ```sh\n   cargo test --features=test-dbs-external -- --nocapture\n   ```\n\n   (The `--no-capture` option isn't required, but shows all the dialects tested\n   per query.)\n\n3. After it's done, remove the containers:\n\n   ```sh\n   cd prqlc/prqlc/tests/integration/dbs && docker compose down\n   ```\n\nNote: on an M1, if the MSSQL docker container doesn't run, refer to\n[this comment](https://github.com/microsoft/mssql-docker/issues/668#issuecomment-1436802153)\n\n## Tested databases\n\nTests are by default run on all the DBs with `SupportLevel::Supported`.\n\nTo test on a DB that is not yet at this support level like `MSSQL`, simply add\n`# mssql:test` on top of the query. To ignore one of the supported DBs like\n`sqlite`, simply add `# sqlite:skip` on top of the query.\n\n## Data\n\nColumns are renamed to `snake_case`, so Postgres and DuckDb don't struggle with\nthem.\n\nFor optimal accessibility, portability between databases and file size, all\ntables are stored as CSV files. Their current size is 432kB, it could be gzip-ed\nto 112kB, but that would require a preprocessing step before running\n`cargo test`.\n\n## Queries\n\nFor databases like ClickHouse, where the order of results is ambiguous, please\nuse `sort` for test queries to to guarantee the order of rows across DBs.\n\nFor example, instead of the following query:\n\n```elm\nfrom albums\n```\n\nUse a query including `sort`:\n\n```elm\nfrom albums\nsort album_id\n```\n\n## Test organization\n\nWe follow the advice in\n<https://matklad.github.io/2021/02/27/delete-cargo-integration-tests.html>.\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/dbs/docker-compose.yaml",
    "content": "services:\n  postgres:\n    # These aren't tagged yet, since there's no dependabot support for\n    # docker-compose yet: https://github.com/dependabot/dependabot-core/issues/390\n    image: \"postgres:alpine\"\n    ports:\n      - \"5432:5432\"\n    environment:\n      POSTGRES_DB: dummy\n      POSTGRES_USER: root\n      POSTGRES_PASSWORD: root\n    volumes: &vol\n      - ../data/chinook:/tmp/chinook:ro\n  mysql:\n    image: \"mysql:oracle\"\n    ports:\n      - \"3306:3306\"\n    environment:\n      MYSQL_DATABASE: dummy\n      MYSQL_ROOT_PASSWORD: root\n    command: --secure-file-priv=\"\"\n    volumes: *vol\n  mssql:\n    image: \"mcr.microsoft.com/mssql/server\"\n    ports:\n      - \"1433:1433\"\n    # https://github.com/microsoft/mssql-docker/issues/668#issuecomment-1436802153\n    platform: linux/amd64\n    environment:\n      ACCEPT_EULA: Y\n      MSSQL_PID: Developer\n      MSSQL_SA_PASSWORD: Wordpass123##\n      LC_ALL: en_US.UTF-8\n      MSSQL_COLLATION: Latin1_General_100_CS_AI_SC_UTF8\n    volumes: *vol\n  clickhouse:\n    # TODO: unpinning this causes an error, would be good to unpin & fix.\n    image: \"clickhouse/clickhouse-server:23.12.4.15-alpine\"\n    ports:\n      # 9004 is MySQL emulation port\n      # https://clickhouse.com/docs/en/guides/sre/network-ports\n      - \"9004:9004\"\n    environment:\n      CLICKHOUSE_DB: dummy\n      # Skip `chown` to user_files_path\n      # https://github.com/ClickHouse/ClickHouse/blob/01c7d2fe719f9b9ed59fce58d5e9dec44167e42f/docker/server/entrypoint.sh#L7-L9\n      CLICKHOUSE_DO_NOT_CHOWN: \"1\"\n    volumes:\n      # ClickHouse can load csv only from user_files_path (default `/var/lib/clickhouse/user_files/`)\n      # https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings#server_configuration_parameters-user_scripts_path\n      - ../data/chinook:/var/lib/clickhouse/user_files/chinook/:ro\n  # TODO: reenable\n  # glaredb:\n  #   build:\n  #     context: dockerfiles\n  #     dockerfile: glaredb.Dockerfile\n  #   ports:\n  #     - \"6543:6543\"\n  #   volumes: *vol\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/dbs/dockerfiles/glaredb.Dockerfile",
    "content": "# syntax=docker/dockerfile:1.4\n\n# TODO: switch to `curlimages/curl`\n# glaredb binary releases are not compatible with alpine\n# So we can't use here now\n# https://github.com/GlareDB/glaredb/issues/1912\nFROM --platform=linux/amd64 docker.io/library/buildpack-deps:stable\n\nRUN <<EOF\ncd /usr/local/bin\ncurl https://glaredb.com/install.sh | sh\nEOF\n\nCMD [ \"glaredb\", \"server\" ]\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/dbs/mod.rs",
    "content": "#![cfg(not(target_family = \"wasm\"))]\n#![cfg(any(feature = \"test-dbs\", feature = \"test-dbs-external\"))]\n/// This has been refactored many times by many people — to the extent that it's\n/// somewhat of a tradition at PRQL...\nmod protocol;\nmod runner;\n\nuse connector_arrow::arrow;\nuse regex::Regex;\n\nuse self::runner::DbTestRunner;\n\npub(crate) fn runners() -> &'static Vec<std::sync::Mutex<Box<dyn DbTestRunner>>> {\n    static RUNNERS: std::sync::OnceLock<Vec<std::sync::Mutex<Box<dyn DbTestRunner>>>> =\n        std::sync::OnceLock::new();\n    RUNNERS.get_or_init(|| {\n        let mut runners = vec![];\n\n        let local_runners: Vec<Box<dyn DbTestRunner>> = vec![\n            Box::new(runner::SQLiteTestRunner::new(\n                \"tests/integration/data/chinook\".to_string(),\n            )),\n            Box::new(runner::DuckDbTestRunner::new(\n                \"tests/integration/data/chinook\".to_string(),\n            )),\n        ];\n        runners.extend(local_runners);\n\n        #[cfg(feature = \"test-dbs-external\")]\n        {\n            let external_runners: Vec<Box<dyn DbTestRunner>> = vec![\n                // I considered putting these URLs into the `Protocol`s; but\n                // - the `url` parameter exists for some-but-not-all of the\n                //   Protocols and given it's a trait implementation the\n                //   signatures need to be the same.\n                // - Similar to the `import_csv` method, different DBs use the\n                //   same protocol but different URLs\n                // We could push them down to the TestRunner structs\n                Box::new(runner::PostgresTestRunner::new(\n                    \"host=localhost user=root password=root dbname=dummy\",\n                    \"/tmp/chinook\".to_string(),\n                )),\n                Box::new(runner::MySqlTestRunner::new(\n                    \"mysql://root:root@localhost:3306/dummy\",\n                    \"/tmp/chinook\".to_string(),\n                )),\n                // TODO: https://github.com/ClickHouse/ClickHouse/issues/69131\n                // Box::new(runner::ClickHouseTestRunner::new(\n                //     \"mysql://default:@localhost:9004/dummy\",\n                //     \"chinook\".to_string(),\n                // )),\n                // TODO: disabled to get CI passing; would like to re-enable\n                // Box::new(runner::GlareDbTestRunner::new(\n                //     \"host=localhost user=glaredb dbname=glaredb port=6543\",\n                //     \"/tmp/chinook\".to_string(),\n                // )),\n                Box::new(runner::MsSqlTestRunner::new(\"/tmp/chinook\".to_string())),\n            ];\n            runners.extend(external_runners);\n        }\n        runners\n            .into_iter()\n            .map(|mut runner| {\n                runner.setup();\n                std::sync::Mutex::new(runner)\n            })\n            .collect()\n    })\n}\n\n/// Converts arrow::RecordBatch into ad-hoc CSV\npub(crate) fn batch_to_csv(batch: arrow::record_batch::RecordBatch) -> String {\n    let mut res = String::with_capacity((batch.num_rows() + 1) * batch.num_columns() * 20);\n\n    // convert each column to string\n    let mut arrays = Vec::with_capacity(batch.num_columns());\n    for col_i in 0..batch.num_columns() {\n        let mut array = batch.columns().get(col_i).unwrap().clone();\n        if *array.data_type() == arrow::datatypes::DataType::Boolean {\n            array = arrow::compute::cast(&array, &arrow::datatypes::DataType::UInt8).unwrap();\n        }\n        let array = arrow::compute::cast(&array, &arrow::datatypes::DataType::Utf8).unwrap();\n        let array = arrow::array::AsArray::as_string::<i32>(&array).clone();\n        arrays.push(array);\n    }\n\n    let re = Regex::new(r\"^-?\\d+\\.\\d*0+$\").unwrap();\n    for row_i in 0..batch.num_rows() {\n        for (i, col) in arrays.iter().enumerate() {\n            let mut value = col.value(row_i);\n\n            // HACK: trim trailing 0\n            if re.is_match(value) {\n                value = value.trim_end_matches('0').trim_end_matches('.');\n            }\n            res.push_str(value);\n            if i < batch.num_columns() - 1 {\n                res.push(',');\n            } else {\n                res.push('\\n');\n            }\n        }\n    }\n\n    res.shrink_to_fit();\n    res\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/dbs/protocol.rs",
    "content": "use anyhow::Result;\nuse connector_arrow::api::{ResultReader, Statement};\nuse connector_arrow::arrow;\nuse connector_arrow::arrow::record_batch::RecordBatch;\n\nfn read_to_batch<'a>(reader: impl ResultReader<'a>) -> Result<RecordBatch> {\n    let batches = reader.into_iter().collect::<Result<Vec<_>, _>>()?;\n    let schema = batches.first().unwrap().schema_ref();\n    Ok(arrow::compute::concat_batches(schema, &batches)?)\n}\n\npub(crate) trait DbProtocol: Send {\n    fn query(&mut self, sql: &str) -> Result<RecordBatch>;\n    fn execute(&mut self, sql: &str) -> Result<()>;\n}\n\nimpl DbProtocol for connector_arrow::rusqlite::SQLiteConnection {\n    fn query(&mut self, sql: &str) -> Result<RecordBatch> {\n        let mut statement = connector_arrow::api::Connector::query(self, sql)?;\n        let reader = statement.start([])?;\n        read_to_batch(reader)\n    }\n\n    fn execute(&mut self, sql: &str) -> Result<()> {\n        self.inner_mut().execute(sql, ())?;\n        Ok(())\n    }\n}\n\nimpl DbProtocol for connector_arrow::duckdb::DuckDBConnection {\n    fn query(&mut self, sql: &str) -> Result<RecordBatch> {\n        let mut statement = connector_arrow::api::Connector::query(self, sql)?;\n        let reader = statement.start([])?;\n        read_to_batch(reader)\n    }\n\n    fn execute(&mut self, sql: &str) -> Result<()> {\n        self.inner_mut().execute(sql, [])?;\n        Ok(())\n    }\n}\n\n#[cfg(feature = \"test-dbs-external\")]\npub(crate) mod external {\n    use super::*;\n    use futures::{AsyncRead, AsyncWrite};\n\n    impl DbProtocol for connector_arrow::postgres::PostgresConnection {\n        fn query(&mut self, sql: &str) -> Result<RecordBatch> {\n            let mut statement = connector_arrow::api::Connector::query(self, sql)?;\n            let reader = statement.start([])?;\n            read_to_batch(reader)\n        }\n\n        fn execute(&mut self, sql: &str) -> Result<()> {\n            self.inner_mut().execute(sql, &[])?;\n            Ok(())\n        }\n    }\n\n    impl DbProtocol for connector_arrow::mysql::MySQLConnection<::mysql::Conn> {\n        fn query(&mut self, sql: &str) -> Result<RecordBatch> {\n            let mut statement = connector_arrow::api::Connector::query(self, sql)?;\n            let reader = statement.start([])?;\n            read_to_batch(reader)\n        }\n\n        fn execute(&mut self, sql: &str) -> Result<()> {\n            use mysql::prelude::Queryable;\n            self.inner_mut().query_iter(sql)?;\n            Ok(())\n        }\n    }\n\n    impl<S: AsyncRead + AsyncWrite + Unpin + Send> DbProtocol\n        for connector_arrow::tiberius::TiberiusConnection<S>\n    {\n        fn query(&mut self, sql: &str) -> Result<RecordBatch> {\n            let mut statement = connector_arrow::api::Connector::query(self, sql)?;\n            let reader = statement.start([])?;\n            read_to_batch(reader)\n        }\n\n        fn execute(&mut self, sql: &str) -> Result<()> {\n            let (rt, client) = self.inner_mut();\n            rt.block_on(client.execute(sql, &[]))?;\n            Ok(())\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/dbs/runner.rs",
    "content": "use anyhow::Result;\nuse connector_arrow::arrow::record_batch::RecordBatch;\nuse glob::glob;\nuse prqlc::sql::Dialect;\n\nuse super::protocol::DbProtocol;\n\nstatic DATA_FILE_ROOT_KEYWORD: &str = \"data_file_root\";\n\npub(crate) trait DbTestRunner: Send {\n    fn dialect(&self) -> Dialect;\n    fn data_file_root(&self) -> &str;\n    fn import_csv(&mut self, csv_path: &str, table_name: &str);\n    fn modify_ddl(&self, sql: String) -> String;\n    fn query(&mut self, prql: &str) -> Result<RecordBatch> {\n        let prql = prql.replace(DATA_FILE_ROOT_KEYWORD, self.data_file_root());\n        let options =\n            prqlc::Options::default().with_target(prqlc::Target::Sql(Some(self.dialect())));\n        let sql = prqlc::compile(&prql, &options)?;\n\n        self.protocol().query(&sql)\n    }\n    fn protocol(&mut self) -> &mut dyn DbProtocol;\n    fn setup(&mut self) {\n        let schema = include_str!(\"../data/chinook/schema.sql\");\n        let statements: Vec<String> = schema\n            .split(';')\n            .map(str::trim)\n            .filter(|s| !s.is_empty())\n            .map(|s| self.modify_ddl(s.to_string()))\n            .collect();\n\n        for statement in statements {\n            self.protocol().execute(&statement).unwrap();\n        }\n\n        for file_path in glob(\"tests/integration/data/chinook/*.csv\").unwrap() {\n            let file_path = file_path.unwrap();\n            let stem = file_path.file_stem().unwrap().to_str().unwrap();\n            let path = format!(\"{}/{}.csv\", self.data_file_root(), stem);\n            self.import_csv(&path, stem);\n        }\n    }\n}\n\npub(crate) struct DuckDbTestRunner {\n    protocol: connector_arrow::duckdb::DuckDBConnection,\n    data_file_root: String,\n}\n\nimpl DuckDbTestRunner {\n    pub(crate) fn new(data_file_root: String) -> Self {\n        let conn = ::duckdb::Connection::open_in_memory().unwrap();\n        let conn_ar = connector_arrow::duckdb::DuckDBConnection::new(conn);\n        Self {\n            protocol: conn_ar,\n            data_file_root,\n        }\n    }\n}\n\nimpl DbTestRunner for DuckDbTestRunner {\n    fn dialect(&self) -> Dialect {\n        Dialect::DuckDb\n    }\n\n    fn protocol(&mut self) -> &mut dyn DbProtocol {\n        &mut self.protocol\n    }\n\n    fn data_file_root(&self) -> &str {\n        &self.data_file_root\n    }\n\n    fn import_csv(&mut self, csv_path: &str, table_name: &str) {\n        self.protocol\n            .execute(&format!(\n                \"COPY {table_name} FROM '{csv_path}' (AUTO_DETECT TRUE);\"\n            ))\n            .unwrap();\n    }\n\n    fn modify_ddl(&self, sql: String) -> String {\n        sql.replace(\"FLOAT\", \"DOUBLE\")\n    }\n}\n\npub(crate) struct SQLiteTestRunner {\n    protocol: connector_arrow::rusqlite::SQLiteConnection,\n    data_file_root: String,\n}\n\nimpl SQLiteTestRunner {\n    pub(crate) fn new(data_file_root: String) -> Self {\n        let conn = rusqlite::Connection::open_in_memory().unwrap();\n        let conn_ar = connector_arrow::rusqlite::SQLiteConnection::new(conn);\n        Self {\n            protocol: conn_ar,\n            data_file_root,\n        }\n    }\n}\n\nimpl DbTestRunner for SQLiteTestRunner {\n    fn dialect(&self) -> Dialect {\n        Dialect::SQLite\n    }\n\n    fn protocol(&mut self) -> &mut dyn DbProtocol {\n        &mut self.protocol\n    }\n\n    fn data_file_root(&self) -> &str {\n        &self.data_file_root\n    }\n\n    fn import_csv(&mut self, csv_path: &str, table_name: &str) {\n        let mut reader = csv::ReaderBuilder::new()\n            .has_headers(true)\n            .from_path(csv_path)\n            .unwrap();\n        let headers = reader\n            .headers()\n            .unwrap()\n            .iter()\n            .map(|s| s.to_string())\n            .collect::<Vec<String>>();\n        for result in reader.records() {\n            let r = result.unwrap();\n            let q = format!(\n                \"INSERT INTO {table_name} ({}) VALUES ({})\",\n                headers.join(\",\"),\n                r.iter()\n                    .map(|s| if s.is_empty() {\n                        \"null\".to_string()\n                    } else {\n                        format!(\"\\\"{}\\\"\", s.replace('\"', \"\\\"\\\"\"))\n                    })\n                    .collect::<Vec<_>>()\n                    .join(\",\")\n            );\n            self.protocol.execute(&q).unwrap();\n        }\n    }\n\n    fn modify_ddl(&self, sql: String) -> String {\n        sql.replace(\"TIMESTAMP\", \"TEXT\") // timestamps in chinook are stored as ISO8601\n    }\n}\n\n#[cfg(feature = \"test-dbs-external\")]\npub(crate) use self::external::*;\n\n#[cfg(feature = \"test-dbs-external\")]\npub(crate) mod external {\n    use super::*;\n    use std::fs;\n\n    pub(crate) struct PostgresTestRunner {\n        protocol: connector_arrow::postgres::PostgresConnection,\n        data_file_root: String,\n    }\n\n    impl PostgresTestRunner {\n        pub(crate) fn new(url: &str, data_file_root: String) -> Self {\n            use connector_arrow::postgres::PostgresConnection;\n            let client = ::postgres::Client::connect(url, ::postgres::NoTls).unwrap();\n            Self {\n                protocol: PostgresConnection::new(client),\n                data_file_root,\n            }\n        }\n    }\n\n    impl DbTestRunner for PostgresTestRunner {\n        fn dialect(&self) -> Dialect {\n            Dialect::Postgres\n        }\n\n        fn protocol(&mut self) -> &mut dyn DbProtocol {\n            &mut self.protocol\n        }\n\n        fn data_file_root(&self) -> &str {\n            &self.data_file_root\n        }\n\n        fn import_csv(&mut self, csv_path: &str, table_name: &str) {\n            self.protocol\n                .execute(&format!(\n                    \"COPY {table_name} FROM '{csv_path}' DELIMITER ',' CSV HEADER;\"\n                ))\n                .unwrap();\n        }\n\n        fn modify_ddl(&self, sql: String) -> String {\n            sql.replace(\"FLOAT\", \"DOUBLE PRECISION\")\n        }\n    }\n\n    pub(crate) struct MySqlTestRunner {\n        protocol: connector_arrow::mysql::MySQLConnection<::mysql::Conn>,\n        data_file_root: String,\n    }\n\n    impl MySqlTestRunner {\n        pub(crate) fn new(url: &str, data_file_root: String) -> Self {\n            let conn = ::mysql::Conn::new(url)\n                .unwrap_or_else(|e| panic!(\"Failed to connect to {url}:\\n{e}\"));\n            Self {\n                protocol: connector_arrow::mysql::MySQLConnection::<::mysql::Conn>::new(conn),\n                data_file_root,\n            }\n        }\n    }\n\n    impl DbTestRunner for MySqlTestRunner {\n        fn dialect(&self) -> Dialect {\n            Dialect::MySql\n        }\n\n        fn protocol(&mut self) -> &mut dyn DbProtocol {\n            &mut self.protocol\n        }\n\n        fn data_file_root(&self) -> &str {\n            &self.data_file_root\n        }\n\n        fn import_csv(&mut self, csv_path: &str, table_name: &str) {\n            // MySQL-specific CSV import logic\n            let csv_path_binding = std::path::PathBuf::from(csv_path);\n            let local_csv_path = format!(\n                \"tests/integration/data/chinook/{}\",\n                csv_path_binding.file_name().unwrap().to_str().unwrap()\n            );\n            let local_old_path = std::path::PathBuf::from(local_csv_path);\n            let mut local_new_path = local_old_path.clone();\n            local_new_path.pop();\n            local_new_path.push(format!(\"{table_name}.my.csv\").as_str());\n            let file_content = fs::read_to_string(local_old_path)\n                .unwrap()\n                .replace(\",,\", \",\\\\N,\")\n                .replace(\",\\n\", \",\\\\N\\n\");\n            fs::write(&local_new_path, file_content).unwrap();\n            self.protocol.execute(\n                &format!(\n                    \"LOAD DATA INFILE '{}' INTO TABLE {table_name} FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\\\"' LINES TERMINATED BY '\\n' IGNORE 1 ROWS;\",\n                    &csv_path_binding.parent().unwrap().join(local_new_path.file_name().unwrap()).to_str().unwrap()\n                )\n            ).unwrap();\n            fs::remove_file(&local_new_path).unwrap();\n        }\n\n        fn modify_ddl(&self, sql: String) -> String {\n            sql.replace(\"TIMESTAMP\", \"DATETIME\")\n        }\n    }\n\n    pub(crate) struct ClickHouseTestRunner {\n        protocol: connector_arrow::mysql::MySQLConnection<::mysql::Conn>,\n        data_file_root: String,\n    }\n\n    impl ClickHouseTestRunner {\n        #[allow(dead_code)]\n        pub(crate) fn new(url: &str, data_file_root: String) -> Self {\n            Self {\n                protocol: connector_arrow::mysql::MySQLConnection::<::mysql::Conn>::new(\n                    ::mysql::Conn::new(url)\n                        .unwrap_or_else(|e| panic!(\"Failed to connect to {url}:\\n{e}\")),\n                ),\n                data_file_root,\n            }\n        }\n    }\n\n    impl DbTestRunner for ClickHouseTestRunner {\n        fn dialect(&self) -> Dialect {\n            Dialect::ClickHouse\n        }\n\n        fn protocol(&mut self) -> &mut dyn DbProtocol {\n            &mut self.protocol\n        }\n\n        fn data_file_root(&self) -> &str {\n            &self.data_file_root\n        }\n\n        fn import_csv(&mut self, csv_path: &str, table_name: &str) {\n            self.protocol\n                .execute(&format!(\n                    \"INSERT INTO {table_name} SELECT * FROM file('{csv_path}')\"\n                ))\n                .unwrap();\n        }\n\n        fn modify_ddl(&self, sql: String) -> String {\n            use regex::Regex;\n            let re = Regex::new(r\"(?s)\\)$\").unwrap();\n            re.replace(&sql, r\") ENGINE = Memory\")\n                .replace(\"TIMESTAMP\", \"DATETIME64\")\n                .replace(\"FLOAT\", \"DOUBLE\")\n                .replace(\"VARCHAR(255)\", \"Nullable(String)\")\n                .to_string()\n        }\n    }\n\n    pub(crate) struct MsSqlTestRunner {\n        protocol: connector_arrow::tiberius::TiberiusConnection<\n            tokio_util::compat::Compat<tokio::net::TcpStream>,\n        >,\n        data_file_root: String,\n    }\n\n    impl MsSqlTestRunner {\n        pub(crate) fn new(data_file_root: String) -> Self {\n            use std::sync::Arc;\n            use tokio_util::compat::TokioAsyncWriteCompatExt;\n\n            let mut config = tiberius::Config::new();\n            config.host(\"localhost\");\n            config.port(1433);\n            config.trust_cert();\n            config.authentication(tiberius::AuthMethod::sql_server(\"sa\", \"Wordpass123##\"));\n\n            let rt = Arc::new(\n                tokio::runtime::Builder::new_current_thread()\n                    .enable_all()\n                    .build()\n                    .unwrap(),\n            );\n\n            let client = rt\n                .block_on(async {\n                    let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;\n                    tcp.set_nodelay(true).unwrap();\n                    tiberius::Client::connect(config, tcp.compat_write()).await\n                })\n                .unwrap();\n\n            Self {\n                protocol: connector_arrow::tiberius::TiberiusConnection::new(rt, client),\n                data_file_root,\n            }\n        }\n    }\n\n    impl DbTestRunner for MsSqlTestRunner {\n        fn dialect(&self) -> Dialect {\n            Dialect::MsSql\n        }\n\n        fn protocol(&mut self) -> &mut dyn DbProtocol {\n            &mut self.protocol\n        }\n\n        fn data_file_root(&self) -> &str {\n            &self.data_file_root\n        }\n\n        fn import_csv(&mut self, csv_path: &str, table_name: &str) {\n            self.protocol.execute(&format!(\n                \"BULK INSERT {table_name} FROM '{csv_path}' WITH (FIRSTROW = 2, FIELDTERMINATOR = ',', ROWTERMINATOR = '\\n', TABLOCK, FORMAT = 'CSV', CODEPAGE = 'RAW');\"\n            )).unwrap();\n        }\n\n        fn modify_ddl(&self, sql: String) -> String {\n            sql.replace(\"TIMESTAMP\", \"DATETIME\")\n                .replace(\"FLOAT\", \"FLOAT(53)\")\n                .replace(\" AS TEXT\", \" AS VARCHAR\")\n        }\n    }\n\n    #[allow(dead_code)]\n    pub(crate) struct GlareDbTestRunner {\n        protocol: connector_arrow::postgres::PostgresConnection,\n        data_file_root: String,\n    }\n\n    impl GlareDbTestRunner {\n        #[allow(dead_code)]\n        pub(crate) fn new(url: &str, data_file_root: String) -> Self {\n            use connector_arrow::postgres::PostgresConnection;\n            let client = ::postgres::Client::connect(url, ::postgres::NoTls).unwrap();\n            Self {\n                protocol: PostgresConnection::new(client),\n                data_file_root,\n            }\n        }\n    }\n\n    impl DbTestRunner for GlareDbTestRunner {\n        fn dialect(&self) -> Dialect {\n            Dialect::GlareDb\n        }\n\n        fn protocol(&mut self) -> &mut dyn DbProtocol {\n            &mut self.protocol\n        }\n\n        fn data_file_root(&self) -> &str {\n            &self.data_file_root\n        }\n\n        fn import_csv(&mut self, csv_path: &str, table_name: &str) {\n            self.protocol\n                .execute(&format!(\n                    \"INSERT INTO {table_name} SELECT * FROM '{csv_path}'\"\n                ))\n                .unwrap();\n        }\n\n        fn modify_ddl(&self, sql: String) -> String {\n            sql.replace(\"FLOAT\", \"DOUBLE PRECISION\")\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/error_messages.rs",
    "content": "//! Test error messages. As of 2023-03, this can hopefully be expanded significantly.\n//! It's also fine to put errors by the things that they're testing.\n//! See also [test_bad_error_messages.rs](test_bad_error_messages.rs) for error\n//! messages which need to be improved.\nuse insta::assert_snapshot;\n\nuse super::sql::compile;\n\n#[test]\nfn test_errors() {\n    assert_snapshot!(compile(r###\"\n    let addadd = a b -> a + b\n\n    from x\n    derive y = (addadd 4 5 6)\n    \"###).unwrap_err(),\n        @r\"\n    Error:\n       ╭─[ :5:17 ]\n       │\n     5 │     derive y = (addadd 4 5 6)\n       │                 ──────┬─────\n       │                       ╰─────── Too many arguments to function `addadd`\n    ───╯\n    \");\n\n    assert_snapshot!(compile(r###\"\n    from a select b\n    \"###).unwrap_err(),\n        @r\"\n    Error:\n       ╭─[ :2:5 ]\n       │\n     2 │     from a select b\n       │     ───────┬───────\n       │            ╰───────── Too many arguments to function `from`\n    ───╯\n    \");\n\n    assert_snapshot!(compile(r###\"\n    from x\n    select a\n    select b\n    \"###).unwrap_err(),\n        @r\"\n    Error:\n       ╭─[ :4:12 ]\n       │\n     4 │     select b\n       │            ┬\n       │            ╰── Unknown name `b`\n       │\n       │ Help: available columns: x.a\n    ───╯\n    \");\n\n    assert_snapshot!(compile(r###\"\n    from employees\n    take 1.8\n    \"###).unwrap_err(),\n        @r\"\n    Error:\n       ╭─[ :3:10 ]\n       │\n     3 │     take 1.8\n       │          ─┬─\n       │           ╰─── `take` expected int or range, but found 1.8\n    ───╯\n    \");\n\n    // LEXER output for Mississippi test (curly quotes are U+2019):\n    use insta::assert_debug_snapshot;\n    let mississippi = \"Mississippi has four S’s and four I’s.\";\n    assert_debug_snapshot!(prqlc_parser::lexer::lex_source(mississippi).unwrap_err(), @r#\"\n    [\n        Error {\n            kind: Error,\n            span: Some(\n                0:22-23,\n            ),\n            reason: Unexpected {\n                found: \"'’'\",\n            },\n            hints: [],\n            code: None,\n        },\n    ]\n    \"#);\n\n    // PARSER output (full compilation error):\n    assert_snapshot!(compile(mississippi).unwrap_err(), @r\"\n    Error:\n       ╭─[ :1:23 ]\n       │\n     1 │ Mississippi has four S’s and four I’s.\n       │                       ┬\n       │                       ╰── unexpected '’'\n    ───╯\n    \");\n\n    assert_snapshot!(compile(\"Answer: T-H-A-T!\").unwrap_err(), @r\"\n    Error:\n       ╭─[ :1:16 ]\n       │\n     1 │ Answer: T-H-A-T!\n       │                ┬\n       │                ╰── expected something else, but found !\n    ───╯\n    \");\n}\n\n#[test]\nfn test_union_all_sqlite() {\n    // TODO: `SQLiteDialect` would be better as `sql.sqlite` or `sqlite`.\n    assert_snapshot!(compile(r###\"\n    prql target:sql.sqlite\n\n    from film\n    remove film2\n    \"###).unwrap_err(), @r\"\n    Error: The dialect SQLiteDialect does not support EXCEPT ALL\n    ↳ Hint: providing more column information will allow the query to be translated to an anti-join.\n    \")\n}\n\n#[test]\nfn test_regex_dialect() {\n    assert_snapshot!(compile(r###\"\n    prql target:sql.mssql\n    from foo\n    filter bar ~= 'love'\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :4:12 ]\n       │\n     4 │     filter bar ~= 'love'\n       │            ──────┬──────\n       │                  ╰──────── operator std.regex_search is not supported for dialect mssql\n    ───╯\n    \")\n}\n\n#[test]\nfn test_bad_function_type() {\n    assert_snapshot!(compile(r###\"\n    from tracks\n    group foo (take)\n    \"###,\n    )\n    .unwrap_err(), @r\"\n    Error:\n       ╭─[ :3:16 ]\n       │\n     3 │     group foo (take)\n       │                ──┬─\n       │                  ╰─── function std.group, param `pipeline` expected type `transform`, but found type `func ? relation -> relation`\n       │\n       │ Help: Type `transform` expands to `func relation -> relation`\n    ───╯\n    \");\n}\n\n#[test]\n#[ignore]\n// FIXME: This would be nice to catch those errors again\n// See https://github.com/PRQL/prql/issues/3127#issuecomment-1849032396\nfn test_basic_type_checking() {\n    assert_snapshot!(compile(r#\"\n    from foo\n    select (a && b) + c\n    \"#)\n    .unwrap_err(), @r###\"\n    Error:\n       ╭─[:3:13]\n       │\n     3 │     select (a && b) + c\n       │             ───┬──\n       │                ╰──── function std.add, param `left` expected type `int || float || timestamp || date`, but found type `bool`\n    ───╯\n    \"###);\n}\n\n#[test]\nfn test_ambiguous() {\n    assert_snapshot!(compile(r#\"\n    from a\n    derive date = x\n    select date\n    \"#)\n    .unwrap_err(), @r\"\n    Error:\n       ╭─[ :4:12 ]\n       │\n     4 │     select date\n       │            ──┬─\n       │              ╰─── Ambiguous name\n       │\n       │ Help: could be any of: std.date, this.date\n       │\n       │ Note: available columns: date\n    ───╯\n    \");\n}\n\n#[test]\nfn test_ambiguous_join() {\n    assert_snapshot!(compile(r#\"\n    from a\n    select x\n    join (from b | select {x}) true\n    select x\n    \"#)\n    .unwrap_err(), @r\"\n    Error:\n       ╭─[ :5:12 ]\n       │\n     5 │     select x\n       │            ┬\n       │            ╰── Ambiguous name\n       │\n       │ Help: could be any of: a.x, b.x\n       │\n       │ Note: available columns: a.x, b.x\n    ───╯\n    \");\n}\n\n#[test]\nfn test_ambiguous_inference() {\n    assert_snapshot!(compile(r#\"\n    from a\n    join b(==b_id)\n    select x\n    \"#)\n    .unwrap_err(), @r\"\n    Error:\n       ╭─[ :4:12 ]\n       │\n     4 │     select x\n       │            ┬\n       │            ╰── Ambiguous name\n       │\n       │ Help: could be any of: a.x, b.x\n    ───╯\n    \");\n}\n\n#[test]\nfn date_to_text_generic() {\n    assert_snapshot!(compile(r#\"\n  [{d = @2021-01-01}]\n  derive {\n    d_str = (d | date.to_text \"%Y/%m/%d\")\n  }\"#).unwrap_err(), @r#\"\n    Error:\n       ╭─[ :4:31 ]\n       │\n     4 │     d_str = (d | date.to_text \"%Y/%m/%d\")\n       │                               ─────┬────\n       │                                    ╰────── Date formatting requires a dialect\n    ───╯\n    \"#);\n}\n\n#[test]\nfn date_to_text_with_column_format() {\n    assert_snapshot!(compile(r#\"\n  from dates_to_display\n  select {my_date, my_format}\n  select {std.date.to_text my_date my_format}\n  \"#).unwrap_err(), @r\"\n    Error:\n       ╭─[ :4:11 ]\n       │\n     4 │   select {std.date.to_text my_date my_format}\n       │           ─────────────────┬────────────────\n       │                            ╰────────────────── `std.date.to_text` only supports a string literal as format\n    ───╯\n    \");\n}\n\n#[test]\nfn date_to_text_unsupported_chrono_item() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.duckdb\n\n    from [{d = @2021-01-01}]\n    derive {\n      d_str = (d | date.to_text \"%_j\")\n    }\"#).unwrap_err(), @r#\"\n    Error:\n       ╭─[ :6:33 ]\n       │\n     6 │       d_str = (d | date.to_text \"%_j\")\n       │                                 ──┬──\n       │                                   ╰──── PRQL doesn't support this format specifier\n    ───╯\n    \"#);\n}\n\n#[test]\nfn available_columns() {\n    assert_snapshot!(compile(r#\"\n    from invoices\n    select foo\n    select bar\n    \"#).unwrap_err(), @r\"\n    Error:\n       ╭─[ :4:12 ]\n       │\n     4 │     select bar\n       │            ─┬─\n       │             ╰─── Unknown name `bar`\n       │\n       │ Help: available columns: invoices.foo\n    ───╯\n    \");\n}\n\n#[test]\nfn empty_interpolations() {\n    assert_snapshot!(compile(r#\"from x | select f\"{}\" \"#).unwrap_err(), @r#\"\n    Error:\n       ╭─[ :1:20 ]\n       │\n     1 │ from x | select f\"{}\"\n       │                    ┬\n       │                    ╰── expected interpolated string variable or '{', but found \"}\"\n    ───╯\n    \"#);\n}\n\n#[test]\nfn no_query_entered() {\n    // Empty query\n    assert_snapshot!(compile(\"\").unwrap_err(), @r\"\n    [E0001] Error: No PRQL query entered\n    \");\n\n    // Comment-only query\n    assert_snapshot!(compile(\"# just a comment\").unwrap_err(), @r\"\n    [E0001] Error: No PRQL query entered\n    \");\n}\n\n#[test]\nfn query_must_begin_with_from() {\n    // Query with declaration but no 'from'\n    assert_snapshot!(compile(\"let x = 5\").unwrap_err(), @r\"\n    [E0001] Error: PRQL queries must begin with 'from'\n    ↳ Hint: A query must start with a 'from' statement to define the main pipeline\n    \");\n\n    // Query with multiple declarations but no pipeline\n    assert_snapshot!(compile(r#\"\n    let x = 5\n    let y = 10\n    \"#).unwrap_err(), @r\"\n    [E0001] Error: PRQL queries must begin with 'from'\n    ↳ Hint: A query must start with a 'from' statement to define the main pipeline\n    \");\n}\n\n#[test]\nfn negative_number_in_transform() {\n    // Negative numbers need to be wrapped in parentheses when used in transforms\n    assert_snapshot!(compile(r###\"\n    from artists\n    sort -name\n    \"###).unwrap_err(), @r\"\n    Error: expected a pipeline that resolves to a table, but found `internal std.sub`\n    ↳ Hint: wrap negative numbers in parentheses, e.g. `sort (-column_name)`\n    \");\n\n    assert_snapshot!(compile(r###\"\n    from pets\n    take -10\n    \"###).unwrap_err(), @r\"\n    Error: expected a pipeline that resolves to a table, but found `internal std.sub`\n    ↳ Hint: wrap negative numbers in parentheses, e.g. `sort (-column_name)`\n    \");\n\n    assert_snapshot!(compile(r###\"\n  from tbl\n  group id (\n    sort -val\n  )\n  \"###).unwrap_err(), @r\"\n    Error: expected a pipeline that resolves to a table, but found `internal std.sub`\n    ↳ Hint: wrap negative numbers in parentheses, e.g. `sort (-column_name)`\n    \");\n}\n\n#[test]\nfn empty_tuple_or_array_from() {\n    assert_snapshot!(compile(r###\"\n    from {}\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :2:10 ]\n       │\n     2 │     from {}\n       │          ─┬\n       │           ╰── expected a table or query, but found an empty tuple `{}`\n    ───╯\n    \");\n\n    assert_snapshot!(compile(r###\"\n    from []\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :2:10 ]\n       │\n     2 │     from []\n       │          ─┬\n       │           ╰── expected a table or query, but found an empty array `[]`\n    ───╯\n    \");\n\n    assert_snapshot!(compile(r###\"\n    from {}\n    select a\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :2:10 ]\n       │\n     2 │     from {}\n       │          ─┬\n       │           ╰── expected a table or query, but found an empty tuple `{}`\n    ───╯\n    \");\n}\n\n#[test]\nfn window_rows_expects_range() {\n    // Issue #5601: window with invalid rows parameter should produce error, not panic\n    assert_snapshot!(compile(r###\"\n    from t\n    group sid (window rows:2 (sid))\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :3:28 ]\n       │\n     3 │     group sid (window rows:2 (sid))\n       │                            ┬\n       │                            ╰── parameter `rows` expected a range, but found 2\n    ───╯\n    \");\n\n    assert_snapshot!(compile(r###\"\n    from t\n    group sid (window range:2 (sid))\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :3:29 ]\n       │\n     3 │     group sid (window range:2 (sid))\n       │                             ┬\n       │                             ╰── parameter `range` expected a range, but found 2\n    ───╯\n    \");\n}\n\n#[test]\nfn bare_lambda_expression() {\n    // Issue #4280: A bare lambda expression like `x -> y` should produce\n    // a clear error, not a confusing internal message.\n    assert_snapshot!(compile(r###\"\n    x -> y\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :2:5 ]\n       │\n     2 │     x -> y\n       │     ───┬──\n       │        ╰──── expected a table, but found a function\n    ───╯\n    \");\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/main.rs",
    "content": "mod bad_error_messages;\nmod dbs;\nmod error_messages;\nmod queries;\nmod sql;\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/project/Project.prql",
    "content": "let favorite_artists = [\n  {artist_id = 120, last_listen = @2023-05-18},\n  {artist_id = 7, last_listen = @2023-05-16},\n]\n\nfavorite_artists\njoin side:left artists.input (==artist_id)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/project/artists.prql",
    "content": "let input = read_parquet \"artists.parquet\"\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/aggregation.prql",
    "content": "# mysql:skip\n# clickhouse:skip\n# glaredb:skip (the string_agg function is not supported)\nfrom tracks\nfilter genre_id == 100\nderive empty_name = name == ''\naggregate {sum track_id, concat_array name, all empty_name, any empty_name}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/append_select.prql",
    "content": "from invoices\nselect { customer_id, invoice_id, billing_country }\ntake 10..15\nappend (\n  from invoices\n  select { customer_id, invoice_id, billing_country }\n  take 40..45\n)\nselect { billing_country, invoice_id }\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/append_select_compute.prql",
    "content": "from invoices\nderive total = case [total < 10 => total * 2, true => total]\nselect { customer_id, invoice_id, total }\ntake 5\nappend (\n  from invoice_items\n  derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\n  select { invoice_line_id, invoice_id, unit_price }\n  take 5\n)\nselect { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql",
    "content": "from invoices\nselect { customer_id, invoice_id, billing_country }\ntake 5\nappend (\n  from employees\n  select { employee_id, employee_id, country }\n  take 5\n)\nappend (\n  from invoice_items\n  select { invoice_line_id, invoice_id, null }\n  take 5\n)\nselect { billing_country, invoice_id }\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/append_select_nulls.prql",
    "content": "from invoices\nselect {an_id = invoice_id, name = null}\ntake 2\nappend (\n  from employees\n  select {an_id = null, name = first_name}\n  take 2\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/append_select_simple.prql",
    "content": "from invoices\nselect { invoice_id, billing_country }\nappend (\n  from invoices\n  select { invoice_id = `invoice_id` + 100, billing_country }\n)\nfilter (billing_country | text.starts_with(\"I\"))\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/arithmetic.prql",
    "content": "# mssql:test\nfrom [\n    { id = 1, x_int =  13, x_float =  13.0, k_int =  5, k_float =  5.0 },\n    { id = 2, x_int = -13, x_float = -13.0, k_int =  5, k_float =  5.0 },\n    { id = 3, x_int =  13, x_float =  13.0, k_int = -5, k_float = -5.0 },\n    { id = 4, x_int = -13, x_float = -13.0, k_int = -5, k_float = -5.0 },\n]\nselect {\n    id,\n\n    x_int / k_int,\n    x_int / k_float,\n    x_float / k_int,\n    x_float / k_float,\n\n    q_ii = x_int // k_int,\n    q_if = x_int // k_float,\n    q_fi = x_float // k_int,\n    q_ff = x_float // k_float,\n\n    r_ii = x_int % k_int,\n    r_if = x_int % k_float,\n    r_fi = x_float % k_int,\n    r_ff = x_float % k_float,\n\n    (q_ii * k_int + r_ii | math.round 0),\n    (q_if * k_float + r_if | math.round 0),\n    (q_fi * k_int + r_fi | math.round 0),\n    (q_ff * k_float + r_ff | math.round 0),\n}\nsort id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/cast.prql",
    "content": "# mssql:test\nfrom tracks\nsort {-bytes}\nselect {\n    name,\n    bin = ((album_id | as REAL) * 99)\n}\ntake 20\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/constants_only.prql",
    "content": "from genres\ntake 10\nfilter true\ntake 20\nfilter true\nselect d = 10\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/date_to_text.prql",
    "content": "# generic:skip\n# glaredb:skip\n# sqlite:skip\n# mssql:test\nfrom invoices\ntake 20\nselect {\n    d1 = (invoice_date | date.to_text \"%Y/%m/%d\"),\n    d2 = (invoice_date | date.to_text \"%F\"),\n    d3 = (invoice_date | date.to_text \"%D\"),\n    d4 = (invoice_date | date.to_text \"%H:%M:%S.%f\"),\n    d5 = (invoice_date | date.to_text \"%r\"),\n    d6 = (invoice_date | date.to_text \"%A %B %-d %Y\"),\n    d7 = (invoice_date | date.to_text \"%a, %-d %b %Y at %I:%M:%S %p\"),\n    d8 = (invoice_date | date.to_text \"%+\"),\n    d9 = (invoice_date | date.to_text \"%-d/%-m/%y\"),\n    d10 = (invoice_date | date.to_text \"%-Hh %Mmin\"),\n    d11 = (invoice_date | date.to_text \"%M'%S\\\"\"),\n    d12 = (invoice_date | date.to_text \"100%% in %d days\"),\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/distinct.prql",
    "content": "# mssql:test\nfrom tracks\nselect {album_id, genre_id}\ngroup tracks.* (take 1)\nsort tracks.*\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/distinct_on.prql",
    "content": "# mssql:test\nfrom tracks\nselect {genre_id, media_type_id, album_id}\ngroup {genre_id, media_type_id} (sort {-album_id} | take 1)\nsort {-genre_id, media_type_id}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/genre_counts.prql",
    "content": "# clickhouse:skip (ClickHouse prefers aliases to column names https://github.com/PRQL/prql/issues/2827)\n# mssql:test\nlet genre_count = (\n    from genres\n    aggregate {a = count name}\n)\n\nfrom genre_count\nfilter a > 0\nselect a = -a\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/group_all.prql",
    "content": "# mssql:test\nfrom a=albums\ntake 10\njoin tracks (==album_id)\ngroup {a.album_id, a.title} (aggregate price = (sum tracks.unit_price | math.round 2))\nsort album_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/group_sort.prql",
    "content": "# mssql:test\nfrom tracks\nderive d = album_id + 1\ngroup d (\n    aggregate {\n        n1 = (track_id | sum),\n    }\n)\nsort d\ntake 10\nselect { d1 = d, n1 }\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/group_sort_derive_select_join.prql",
    "content": "s\"SELECT album_id,title,artist_id FROM albums\"\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\nsort {this.artist_id, this.album_title_count}\nderive {new_album_count = this.album_title_count}\nselect {this.artist_id, this.new_album_count}\njoin side:left ( s\"SELECT artist_id,name as artist_name FROM artists\" ) (this.artist_id == that.artist_id)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/group_sort_filter_derive_select_join.prql",
    "content": "s\"SELECT album_id,title,artist_id FROM albums\"\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\nsort {this.artist_id, this.album_title_count}\nfilter (this.album_title_count) > 10\nderive {new_album_count = this.album_title_count}\nselect {this.artist_id, this.new_album_count}\njoin side:left ( s\"SELECT artist_id,name as artist_name FROM artists\" ) (this.artist_id == that.artist_id)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/group_sort_limit_take.prql",
    "content": "# Compute the 3 longest songs for each genre and sort by genre\n# mssql:test\nfrom tracks\nselect {genre_id,milliseconds}\ngroup {genre_id} (\n  sort {-milliseconds}\n  take 3\n)\njoin genres (==genre_id)\nselect {name, milliseconds}\nsort {+name,-milliseconds}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/invoice_totals.prql",
    "content": "# clickhouse:skip (clickhouse doesn't have lag function)\n\n#! Calculate a number of metrics about the sales of tracks in each city.\nfrom i=invoices\njoin ii=invoice_items (==invoice_id)\nderive {\n    city = i.billing_city,\n    street = i.billing_address,\n}\ngroup {city, street} (\n    derive total = ii.unit_price * ii.quantity\n    aggregate {\n        num_orders = count_distinct i.invoice_id,\n        num_tracks = sum ii.quantity,\n        total_price = sum total,\n    }\n)\ngroup {city} (\n    sort street\n    window expanding:true (\n        derive {running_total_num_tracks = sum num_tracks}\n    )\n)\nsort {city, street}\nderive {num_tracks_last_week = lag 7 num_tracks}\nselect {\n    city,\n    street,\n    num_orders,\n    num_tracks,\n    running_total_num_tracks,\n    num_tracks_last_week\n}\ntake 20\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/loop_01.prql",
    "content": "# clickhouse:skip (DB::Exception: Syntax error)\n# glaredb:skip (DataFusion does not support recursive CTEs https://github.com/apache/arrow-datafusion/issues/462)\nfrom [{n = 1}]\nselect n = n - 2\nloop (filter n < 4 | select n = n + 1)\nselect n = n * 2\nsort n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/math_module.prql",
    "content": "# mssql:test\n# sqlite:skip (see https://github.com/rusqlite/rusqlite/issues/1211)\nfrom invoices\ntake 5\nselect {\n    total_original = (total | math.round 2),\n    total_x = (math.pi - total | math.round 2 | math.abs),\n    total_floor = (math.floor total),\n    total_ceil = (math.ceil total),\n    total_log10 = (math.log10 total | math.round 3),\n    total_log2 = (math.log 2 total | math.round 3),\n    total_sqrt = (math.sqrt total | math.round 3),\n    total_ln = (math.ln total | math.exp | math.round 2),\n    total_cos = (math.cos total | math.acos | math.round 2),\n    total_sin = (math.sin total | math.asin | math.round 2),\n    total_tan = (math.tan total | math.atan | math.round 2),\n    total_deg = (total | math.degrees | math.radians | math.round 2),\n    total_square = (total | math.pow 2 | math.round 2),\n    total_square_op = ((total ** 2) | math.round 2),\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/pipelines.prql",
    "content": "# sqlite:skip (Only works on Sqlite implementations which have the extension\n# installed\n# https://stackoverflow.com/questions/24037982/how-to-use-regexp-in-sqlite)\n\nfrom tracks\n\nfilter (name ~= \"Love\")\nfilter ((milliseconds / 1000 / 60) | in 3..4)\nsort track_id\ntake 1..15\nselect {name, composer}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/read_csv.prql",
    "content": "# sqlite:skip\n# postgres:skip\n# mysql:skip\nfrom (read_csv \"data_file_root/media_types.csv\")\nappend (read_json \"data_file_root/media_types.json\")\nsort media_type_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/set_ops_remove.prql",
    "content": "# mssql:test\nlet distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\n\nfrom_text format:json '{ \"columns\": [\"a\"], \"data\": [[1], [2], [2], [3]] }'\ndistinct\nremove (from_text format:json '{ \"columns\": [\"a\"], \"data\": [[1], [2]] }')\nsort a\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/sort.prql",
    "content": "# mssql:test\nfrom e=employees\nfilter first_name != \"Mitchell\"\nsort {first_name, last_name}\n\n# joining may use HashMerge, which can undo ORDER BY\njoin manager=employees side:left (e.reports_to == manager.employee_id)\n\nselect {e.first_name, e.last_name, manager.first_name}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/sort_2.prql",
    "content": "from albums\nselect { AA=album_id, artist_id }\nsort AA\nfilter AA >= 25\njoin artists (==artist_id)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/sort_3.prql",
    "content": "from [{track_id=0, album_id=1, genre_id=2}]\nselect { AA=track_id, album_id, genre_id }\nsort AA\njoin side:left [{album_id=1, album_title=\"Songs\"}] (==album_id)\nselect { AA, AT = album_title ?? \"unknown\", genre_id }\nfilter AA < 25\njoin side:left [{genre_id=1, genre_title=\"Rock\"}] (==genre_id)\nselect { AA, AT, GT = genre_title ?? \"unknown\" }\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/switch.prql",
    "content": "# glaredb:skip (May be a bag of String type conversion for Postgres Client)\n# mssql:test\nfrom tracks\nsort milliseconds\nselect display = case [\n    composer != null => composer,\n    genre_id < 17 => 'no composer',\n    true => f'unknown composer'\n]\ntake 10\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/take.prql",
    "content": "# mssql:test\nfrom tracks\nsort {+track_id}\ntake 3..5\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/text_module.prql",
    "content": "# mssql:test\n# glaredb:skip — TODO: started raising an error on 2024-05-20; see `window.prql`\n# for more details\nfrom albums\nselect {\n    title,\n    title_and_spaces = f\"  {title}  \",\n    low = (title | text.lower),\n    up = (title | text.upper),\n    ltrimmed = (title | text.ltrim),\n    rtrimmed = (title | text.rtrim),\n    trimmed = (title | text.trim),\n    len = (title | text.length),\n    subs = (title | text.extract 2 5),\n    replace = (title | text.replace \"al\" \"PIKA\"),\n}\nsort {title}\nfilter (title | text.starts_with \"Black\") || (title | text.contains \"Sabbath\") || (title | text.ends_with \"os\")\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries/window.prql",
    "content": "# clickhouse:skip problems with DISTINCT ON\n# glaredb:skip — TODO: started raising an error on 2024-05-20, from https://github.com/PRQL/prql/actions/runs/9154902656/job/25198160283:\n    # ERROR: This feature is not implemented: Unsupported ast node in sqltorel:\n    # Substring { expr: Identifier(Ident { value: \"title\", quote_style: None }),\n    # substring_from: Some(Value(Number(\"2\", false))), substring_for:\n    # Some(Value(Number(\"5\", false))), special: true }\nfrom tracks\ngroup genre_id (\n  sort milliseconds\n  derive {\n    num = row_number this,\n    total = count this,\n    last_val = last track_id,\n  }\n  take 10\n)\nsort {genre_id, milliseconds}\nselect {track_id, genre_id, num, total, last_val}\nfilter genre_id >= 22\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/queries.rs",
    "content": "#![cfg(not(target_family = \"wasm\"))]\nuse std::fs;\nuse std::path::Path;\n\nuse insta::assert_debug_snapshot;\nuse insta::{assert_snapshot, with_settings};\nuse prqlc::sql::Dialect;\nuse prqlc::sql::SupportLevel;\nuse prqlc::{Options, Target};\nuse test_each_file::test_each_path;\n\nmod lex {\n    use super::*;\n\n    test_each_path! { in \"./prqlc/prqlc/tests/integration/queries\" => run }\n\n    fn run(prql_path: &Path) {\n        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();\n        let prql = fs::read_to_string(prql_path).unwrap();\n\n        let tokens = prqlc_parser::lexer::lex_source(&prql).unwrap();\n\n        with_settings!({ input_file => prql_path }, {\n            assert_debug_snapshot!(test_name, tokens)\n        });\n    }\n}\n\nmod compile {\n    use super::*;\n\n    // If this is giving compilation errors saying `expected identifier, found keyword`,\n    // rename the filenames in queries to something that's not a keyword in Rust.\n    test_each_path! { in \"./prqlc/prqlc/tests/integration/queries\" => run }\n\n    fn run(prql_path: &Path) {\n        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();\n        let prql = fs::read_to_string(prql_path).unwrap();\n        if prql.contains(\"generic:skip\") {\n            return;\n        }\n\n        let target = Target::Sql(Some(Dialect::Generic));\n        let options = Options::default().no_signature().with_target(target);\n\n        let sql = prqlc::compile(&prql, &options).unwrap();\n\n        with_settings!({ input_file => prql_path }, {\n            assert_snapshot!(test_name, &sql, &prql)\n        });\n    }\n}\n\nfn should_run_query(dialect: Dialect, prql: &str) -> bool {\n    let dialect_str = dialect.to_string().to_lowercase();\n\n    match dialect.support_level() {\n        SupportLevel::Supported => !prql.contains(&format!(\"{dialect_str}:skip\")),\n        SupportLevel::Unsupported => prql.contains(&format!(\"{dialect_str}:test\")),\n        SupportLevel::Nascent => false,\n    }\n}\n\nmod compileall {\n    use super::*;\n    use similar::TextDiff;\n    use strum::IntoEnumIterator;\n\n    test_each_path! { in \"./prqlc/prqlc/tests/integration/queries\" => run }\n\n    fn run(prql_path: &Path) {\n        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();\n        let prql = fs::read_to_string(prql_path).unwrap();\n        if prql.contains(\"generic:skip\") {\n            return;\n        }\n\n        // first compile with the generic dialect\n        let target = Target::Sql(Some(Dialect::Generic));\n        let options = Options::default().no_signature().with_target(target);\n\n        let generic_sql = prqlc::compile(&prql, &options).unwrap();\n\n        // next compile with each dialect\n        let mut diffsnap = \"\".to_owned();\n        for dialect in Dialect::iter() {\n            if !should_run_query(dialect, &prql) {\n                continue;\n            }\n\n            let dialect_target = Target::Sql(Some(dialect));\n            let dialect_options = Options::default()\n                .no_signature()\n                .with_target(dialect_target);\n\n            let dialect_sql = prqlc::compile(&prql, &dialect_options).unwrap();\n\n            let diff = TextDiff::from_lines(&generic_sql, &dialect_sql);\n            diffsnap = format!(\n                \"{diffsnap}\\n{}\",\n                diff.unified_diff()\n                    .context_radius(10)\n                    .header(\"generic\", &dialect.to_string())\n            );\n        }\n        with_settings!({ input_file => prql_path }, {\n            assert_snapshot!(test_name, diffsnap, &prql)\n        });\n    }\n}\n\nmod fmt {\n    use super::*;\n\n    test_each_path! { in \"./prqlc/prqlc/tests/integration/queries\" => run }\n\n    fn run(prql_path: &Path) {\n        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();\n        let prql = fs::read_to_string(prql_path).unwrap();\n\n        let pl = prqlc::prql_to_pl(&prql).unwrap();\n        let formatted = prqlc::pl_to_prql(&pl).unwrap();\n\n        with_settings!({ input_file => prql_path }, {\n            assert_snapshot!(test_name, &formatted, &prql)\n        });\n\n        // Check the formatted queries can still compile\n        prqlc::prql_to_pl(&formatted).unwrap();\n    }\n}\n\n#[cfg(feature = \"serde_yaml\")]\nmod debug_lineage {\n    use super::*;\n\n    test_each_path! { in \"./prqlc/prqlc/tests/integration/queries\" => run }\n\n    fn run(prql_path: &Path) {\n        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();\n        let prql = fs::read_to_string(prql_path).unwrap();\n\n        let pl = prqlc::prql_to_pl(&prql).unwrap();\n        let fc = prqlc::internal::pl_to_lineage(pl).unwrap();\n\n        let lineage = serde_yaml::to_string(&fc).unwrap();\n\n        with_settings!({ input_file => prql_path }, {\n            assert_snapshot!(test_name, &lineage, &prql)\n        });\n    }\n}\n\n#[cfg(any(feature = \"test-dbs\", feature = \"test-dbs-external\"))]\nmod results {\n\n    use itertools::Itertools;\n\n    use super::*;\n    use crate::dbs::{batch_to_csv, runners};\n\n    test_each_path! { in \"./prqlc/prqlc/tests/integration/queries\" => run }\n\n    fn run(prql_path: &Path) {\n        let test_name = prql_path.file_stem().unwrap().to_str().unwrap();\n        let prql = fs::read_to_string(prql_path).unwrap();\n\n        // for each of the runners, get the query\n        let results: Vec<(Dialect, String)> = runners()\n            .iter()\n            .filter_map(|runner| {\n                let mut runner = runner.lock().unwrap();\n                let dialect = runner.dialect();\n                if !should_run_query(dialect, &prql) {\n                    return None;\n                }\n\n                eprintln!(\"Executing {test_name} for {dialect}\");\n\n                match runner.query(&prql) {\n                    Ok(batch) => {\n                        let csv = batch_to_csv(batch);\n                        Some(Ok((dialect, csv)))\n                    }\n                    Err(e) => Some(Err(e)),\n                }\n            })\n            .try_collect()\n            .unwrap();\n\n        if results.is_empty() {\n            panic!(\"No valid dialects to run the query at {prql_path:#?} against\");\n        }\n\n        // similar to `insta::allow_duplicates!`, but with reporting of which two cases are\n        // not matching.\n        let ((first_dialect, first_text), others) = results.split_first().unwrap();\n\n        with_settings!({ input_file => prql_path }, {\n            assert_snapshot!(test_name, first_text, &prql)\n        });\n\n        for (dialect, text) in others {\n            similar_asserts::assert_eq!(\n                first_text,\n                text,\n                \"{} {} {}\",\n                test_name,\n                first_dialect,\n                dialect\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__aggregation.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mysql:skip\\n# clickhouse:skip\\n# glaredb:skip (the string_agg function is not supported)\\nfrom tracks\\nfilter genre_id == 100\\nderive empty_name = name == ''\\naggregate {sum track_id, concat_array name, all empty_name, any empty_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/aggregation.prql\n---\nSELECT\n  COALESCE(SUM(track_id), 0),\n  COALESCE(STRING_AGG(name, ''), ''),\n  COALESCE(BOOL_AND(name = ''), TRUE),\n  COALESCE(BOOL_OR(name = ''), FALSE)\nFROM\n  tracks\nWHERE\n  genre_id = 100\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 10..15\\nappend (\\n  from invoices\\n  select { customer_id, invoice_id, billing_country }\\n  take 40..45\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select.prql\nsnapshot_kind: text\n---\nSELECT\n  *\nFROM\n  (\n    SELECT\n      billing_country,\n      invoice_id\n    FROM\n      invoices\n    LIMIT\n      6 OFFSET 9\n  ) AS table_2\nUNION\nALL\nSELECT\n  *\nFROM\n  (\n    SELECT\n      billing_country,\n      invoice_id\n    FROM\n      invoices\n    LIMIT\n      6 OFFSET 39\n  ) AS table_3\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_compute.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nderive total = case [total < 10 => total * 2, true => total]\\nselect { customer_id, invoice_id, total }\\ntake 5\\nappend (\\n  from invoice_items\\n  derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\\n  select { invoice_line_id, invoice_id, unit_price }\\n  take 5\\n)\\nselect { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_compute.prql\nsnapshot_kind: text\n---\nWITH table_1 AS (\n  SELECT\n    *\n  FROM\n    (\n      SELECT\n        invoice_id,\n        CASE\n          WHEN total < 10 THEN total * 2\n          ELSE total\n        END AS _expr_0,\n        customer_id\n      FROM\n        invoices\n      LIMIT\n        5\n    ) AS table_3\n  UNION\n  ALL\n  SELECT\n    *\n  FROM\n    (\n      SELECT\n        invoice_id,\n        CASE\n          WHEN unit_price < 1 THEN unit_price * 2\n          ELSE unit_price\n        END AS unit_price,\n        invoice_line_id\n      FROM\n        invoice_items\n      LIMIT\n        5\n    ) AS table_4\n)\nSELECT\n  customer_id * 2 AS a,\n  ROUND(invoice_id * _expr_0, 1) AS b\nFROM\n  table_1\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_multiple_with_null.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 5\\nappend (\\n  from employees\\n  select { employee_id, employee_id, country }\\n  take 5\\n)\\nappend (\\n  from invoice_items\\n  select { invoice_line_id, invoice_id, null }\\n  take 5\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql\nsnapshot_kind: text\n---\nSELECT\n  *\nFROM\n  (\n    SELECT\n      billing_country,\n      invoice_id\n    FROM\n      invoices\n    LIMIT\n      5\n  ) AS table_4\nUNION\nALL\nSELECT\n  *\nFROM\n  (\n    SELECT\n      country,\n      employee_id\n    FROM\n      employees\n    LIMIT\n      5\n  ) AS table_5\nUNION\nALL\nSELECT\n  *\nFROM\n  (\n    SELECT\n      NULL,\n      invoice_id\n    FROM\n      invoice_items\n    LIMIT\n      5\n  ) AS table_6\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_nulls.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# duckdb:skip\\n# postgres:skip\\n\\nfrom invoices\\nselect {an_id = invoice_id, name = null}\\ntake 2\\nappend (\\n  from employees\\n  select {an_id = null, name = first_name}\\n  take 2\\n)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_nulls.prql\n---\nSELECT\n  *\nFROM\n  (\n    SELECT\n      invoice_id AS an_id,\n      NULL AS name\n    FROM\n      invoices\n    LIMIT\n      2\n  ) AS table_2\nUNION\nALL\nSELECT\n  *\nFROM\n  (\n    SELECT\n      NULL AS an_id,\n      first_name AS name\n    FROM\n      employees\n    LIMIT\n      2\n  ) AS table_3\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_simple.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { invoice_id, billing_country }\\nappend (\\n  from invoices\\n  select { invoice_id = `invoice_id` + 100, billing_country }\\n)\\nfilter (billing_country | text.starts_with(\\\"I\\\"))\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_simple.prql\n---\nWITH table_1 AS (\n  SELECT\n    invoice_id,\n    billing_country\n  FROM\n    invoices\n  UNION\n  ALL\n  SELECT\n    invoice_id + 100 AS invoice_id,\n    billing_country\n  FROM\n    invoices\n)\nSELECT\n  invoice_id,\n  billing_country\nFROM\n  table_1\nWHERE\n  billing_country LIKE CONCAT('I', '%')\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__arithmetic.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom [\\n    { id = 1, x_int =  13, x_float =  13.0, k_int =  5, k_float =  5.0 },\\n    { id = 2, x_int = -13, x_float = -13.0, k_int =  5, k_float =  5.0 },\\n    { id = 3, x_int =  13, x_float =  13.0, k_int = -5, k_float = -5.0 },\\n    { id = 4, x_int = -13, x_float = -13.0, k_int = -5, k_float = -5.0 },\\n]\\nselect {\\n    id,\\n\\n    x_int / k_int,\\n    x_int / k_float,\\n    x_float / k_int,\\n    x_float / k_float,\\n\\n    q_ii = x_int // k_int,\\n    q_if = x_int // k_float,\\n    q_fi = x_float // k_int,\\n    q_ff = x_float // k_float,\\n\\n    r_ii = x_int % k_int,\\n    r_if = x_int % k_float,\\n    r_fi = x_float % k_int,\\n    r_ff = x_float % k_float,\\n\\n    (q_ii * k_int + r_ii | math.round 0),\\n    (q_if * k_float + r_if | math.round 0),\\n    (q_fi * k_int + r_fi | math.round 0),\\n    (q_ff * k_float + r_ff | math.round 0),\\n}\\nsort id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/arithmetic.prql\n---\nWITH table_0 AS (\n  SELECT\n    1 AS id,\n    13 AS x_int,\n    13.0 AS x_float,\n    5 AS k_int,\n    5.0 AS k_float\n  UNION\n  ALL\n  SELECT\n    2 AS id,\n    -13 AS x_int,\n    -13.0 AS x_float,\n    5 AS k_int,\n    5.0 AS k_float\n  UNION\n  ALL\n  SELECT\n    3 AS id,\n    13 AS x_int,\n    13.0 AS x_float,\n    -5 AS k_int,\n    -5.0 AS k_float\n  UNION\n  ALL\n  SELECT\n    4 AS id,\n    -13 AS x_int,\n    -13.0 AS x_float,\n    -5 AS k_int,\n    -5.0 AS k_float\n)\nSELECT\n  id,\n  x_int / k_int,\n  x_int / k_float,\n  x_float / k_int,\n  x_float / k_float,\n  FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n  FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) AS q_if,\n  FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n  FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n  x_int % k_int AS r_ii,\n  x_int % k_float AS r_if,\n  x_float % k_int AS r_fi,\n  x_float % k_float AS r_ff,\n  ROUND(\n    FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n    0\n  ),\n  ROUND(\n    FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) * k_float + x_int % k_float,\n    0\n  ),\n  ROUND(\n    FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) * k_int + x_float % k_int,\n    0\n  ),\n  ROUND(\n    FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) * k_float + x_float % k_float,\n    0\n  )\nFROM\n  table_0\nORDER BY\n  id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__cast.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {-bytes}\\nselect {\\n    name,\\n    bin = ((album_id | as REAL) * 99)\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/cast.prql\n---\nWITH table_0 AS (\n  SELECT\n    name,\n    CAST(album_id AS REAL) * 99 AS bin,\n    bytes\n  FROM\n    tracks\n  ORDER BY\n    bytes DESC\n  LIMIT\n    20\n)\nSELECT\n  name,\n  bin\nFROM\n  table_0\nORDER BY\n  bytes DESC\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__constants_only.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from genres\\ntake 10\\nfilter true\\ntake 20\\nfilter true\\nselect d = 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/constants_only.prql\n---\nWITH table_1 AS (\n  SELECT\n    NULL\n  FROM\n    genres\n  LIMIT\n    10\n), table_0 AS (\n  SELECT\n    NULL\n  FROM\n    table_1\n  WHERE\n    true\n  LIMIT\n    20\n)\nSELECT\n  10 AS d\nFROM\n  table_0\nWHERE\n  true\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__distinct.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {album_id, genre_id}\\ngroup tracks.* (take 1)\\nsort tracks.*\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct.prql\n---\nWITH table_0 AS (\n  SELECT\n    DISTINCT album_id,\n    genre_id\n  FROM\n    tracks\n)\nSELECT\n  album_id,\n  genre_id\nFROM\n  table_0\nORDER BY\n  album_id,\n  genre_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__distinct_on.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {genre_id, media_type_id, album_id}\\ngroup {genre_id, media_type_id} (sort {-album_id} | take 1)\\nsort {-genre_id, media_type_id}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct_on.prql\n---\nWITH table_0 AS (\n  SELECT\n    genre_id,\n    media_type_id,\n    album_id,\n    ROW_NUMBER() OVER (\n      PARTITION BY genre_id,\n      media_type_id\n      ORDER BY\n        album_id DESC\n    ) AS _expr_0\n  FROM\n    tracks\n)\nSELECT\n  genre_id,\n  media_type_id,\n  album_id\nFROM\n  table_0\nWHERE\n  _expr_0 <= 1\nORDER BY\n  genre_id DESC,\n  media_type_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__genre_counts.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (ClickHouse prefers aliases to column names https://github.com/PRQL/prql/issues/2827)\\n# mssql:test\\nlet genre_count = (\\n    from genres\\n    aggregate {a = count name}\\n)\\n\\nfrom genre_count\\nfilter a > 0\\nselect a = -a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/genre_counts.prql\n---\nWITH genre_count AS (\n  SELECT\n    COUNT(*) AS a\n  FROM\n    genres\n)\nSELECT\n  - a AS a\nFROM\n  genre_count\nWHERE\n  a > 0\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__group_all.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom a=albums\\ntake 10\\njoin tracks (==album_id)\\ngroup {a.album_id, a.title} (aggregate price = (sum tracks.unit_price | math.round 2))\\nsort album_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_all.prql\n---\nWITH table_0 AS (\n  SELECT\n    album_id,\n    title\n  FROM\n    albums AS a\n  LIMIT\n    10\n)\nSELECT\n  table_0.album_id,\n  table_0.title,\n  ROUND(COALESCE(SUM(tracks.unit_price), 0), 2) AS price\nFROM\n  table_0\n  INNER JOIN tracks ON table_0.album_id = tracks.album_id\nGROUP BY\n  table_0.album_id,\n  table_0.title\nORDER BY\n  table_0.album_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__group_sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nderive d = album_id + 1\\ngroup d (\\n    aggregate {\\n        n1 = (track_id | sum),\\n    }\\n)\\nsort d\\ntake 10\\nselect { d1 = d, n1 }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort.prql\n---\nWITH table_0 AS (\n  SELECT\n    COALESCE(SUM(track_id), 0) AS n1,\n    album_id + 1 AS _expr_0\n  FROM\n    tracks\n  GROUP BY\n    album_id + 1\n),\ntable_1 AS (\n  SELECT\n    _expr_0 AS d1,\n    n1,\n    _expr_0\n  FROM\n    table_0\n  ORDER BY\n    _expr_0\n  LIMIT\n    10\n)\nSELECT\n  d1,\n  n1\nFROM\n  table_1\nORDER BY\n  d1\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__group_sort_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_derive_select_join.prql\n---\nWITH table_0 AS (\n  SELECT\n    album_id,\n    title,\n    artist_id\n  FROM\n    albums\n),\ntable_4 AS (\n  SELECT\n    artist_id,\n    COUNT(*) AS _expr_0\n  FROM\n    table_0\n  GROUP BY\n    artist_id\n),\ntable_2 AS (\n  SELECT\n    artist_id,\n    _expr_0 AS new_album_count,\n    _expr_0\n  FROM\n    table_4\n),\ntable_1 AS (\n  SELECT\n    artist_id,\n    name as artist_name\n  FROM\n    artists\n),\ntable_3 AS (\n  SELECT\n    table_2.artist_id,\n    table_2.new_album_count,\n    table_1.artist_id AS _expr_1,\n    table_1.artist_name,\n    table_2._expr_0\n  FROM\n    table_2\n    LEFT OUTER JOIN table_1 ON table_2.artist_id = table_1.artist_id\n)\nSELECT\n  artist_id,\n  new_album_count,\n  _expr_1,\n  artist_name\nFROM\n  table_3\nORDER BY\n  artist_id,\n  new_album_count\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__group_sort_filter_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nfilter (this.album_title_count) > 10\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_filter_derive_select_join.prql\n---\nWITH table_0 AS (\n  SELECT\n    album_id,\n    title,\n    artist_id\n  FROM\n    albums\n),\ntable_3 AS (\n  SELECT\n    artist_id,\n    COUNT(*) AS _expr_0\n  FROM\n    table_0\n  GROUP BY\n    artist_id\n),\ntable_4 AS (\n  SELECT\n    artist_id,\n    _expr_0 AS new_album_count,\n    _expr_0\n  FROM\n    table_3\n  WHERE\n    _expr_0 > 10\n),\ntable_2 AS (\n  SELECT\n    artist_id,\n    new_album_count,\n    _expr_0\n  FROM\n    table_4\n),\ntable_1 AS (\n  SELECT\n    artist_id,\n    name as artist_name\n  FROM\n    artists\n)\nSELECT\n  table_2.artist_id,\n  table_2.new_album_count,\n  table_1.artist_id,\n  table_1.artist_name\nFROM\n  table_2\n  LEFT OUTER JOIN table_1 ON table_2.artist_id = table_1.artist_id\nORDER BY\n  table_2.artist_id,\n  table_2.new_album_count\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__group_sort_limit_take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# Compute the 3 longest songs for each genre and sort by genre\\n# mssql:test\\nfrom tracks\\nselect {genre_id,milliseconds}\\ngroup {genre_id} (\\n  sort {-milliseconds}\\n  take 3\\n)\\njoin genres (==genre_id)\\nselect {name, milliseconds}\\nsort {+name,-milliseconds}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_limit_take.prql\n---\nWITH table_1 AS (\n  SELECT\n    milliseconds,\n    genre_id,\n    ROW_NUMBER() OVER (\n      PARTITION BY genre_id\n      ORDER BY\n        milliseconds DESC\n    ) AS _expr_0\n  FROM\n    tracks\n),\ntable_0 AS (\n  SELECT\n    milliseconds,\n    genre_id\n  FROM\n    table_1\n  WHERE\n    _expr_0 <= 3\n)\nSELECT\n  genres.name,\n  table_0.milliseconds\nFROM\n  table_0\n  INNER JOIN genres ON table_0.genre_id = genres.genre_id\nORDER BY\n  genres.name,\n  table_0.milliseconds DESC\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__invoice_totals.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (clickhouse doesn't have lag function)\\n\\n#! Calculate a number of metrics about the sales of tracks in each city.\\nfrom i=invoices\\njoin ii=invoice_items (==invoice_id)\\nderive {\\n    city = i.billing_city,\\n    street = i.billing_address,\\n}\\ngroup {city, street} (\\n    derive total = ii.unit_price * ii.quantity\\n    aggregate {\\n        num_orders = count_distinct i.invoice_id,\\n        num_tracks = sum ii.quantity,\\n        total_price = sum total,\\n    }\\n)\\ngroup {city} (\\n    sort street\\n    window expanding:true (\\n        derive {running_total_num_tracks = sum num_tracks}\\n    )\\n)\\nsort {city, street}\\nderive {num_tracks_last_week = lag 7 num_tracks}\\nselect {\\n    city,\\n    street,\\n    num_orders,\\n    num_tracks,\\n    running_total_num_tracks,\\n    num_tracks_last_week\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/invoice_totals.prql\n---\nWITH table_0 AS (\n  SELECT\n    i.billing_city AS city,\n    i.billing_address AS street,\n    COUNT(DISTINCT i.invoice_id) AS num_orders,\n    COALESCE(SUM(ii.quantity), 0) AS num_tracks\n  FROM\n    invoices AS i\n    INNER JOIN invoice_items AS ii ON i.invoice_id = ii.invoice_id\n  GROUP BY\n    i.billing_city,\n    i.billing_address\n)\nSELECT\n  city,\n  street,\n  num_orders,\n  num_tracks,\n  SUM(num_tracks) OVER (\n    PARTITION BY city\n    ORDER BY\n      street ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n  ) AS running_total_num_tracks,\n  LAG(num_tracks, 7) OVER (\n    ORDER BY\n      city,\n      street\n  ) AS num_tracks_last_week\nFROM\n  table_0\nORDER BY\n  city,\n  street\nLIMIT\n  20\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__loop_01.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (DB::Exception: Syntax error)\\n# glaredb:skip (DataFusion does not support recursive CTEs https://github.com/apache/arrow-datafusion/issues/462)\\nfrom [{n = 1}]\\nselect n = n - 2\\nloop (filter n < 4 | select n = n + 1)\\nselect n = n * 2\\nsort n\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/loop_01.prql\n---\nWITH RECURSIVE table_0 AS (\n  SELECT\n    1 AS n\n),\ntable_1 AS (\n  SELECT\n    n - 2 AS _expr_0\n  FROM\n    table_0\n  UNION\n  ALL\n  SELECT\n    _expr_0 + 1\n  FROM\n    table_1\n  WHERE\n    _expr_0 < 4\n)\nSELECT\n  _expr_0 * 2 AS n\nFROM\n  table_1 AS table_2\nORDER BY\n  n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__math_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# sqlite:skip (see https://github.com/rusqlite/rusqlite/issues/1211)\\nfrom invoices\\ntake 5\\nselect {\\n    total_original = (total | math.round 2),\\n    total_x = (math.pi - total | math.round 2 | math.abs),\\n    total_floor = (math.floor total),\\n    total_ceil = (math.ceil total),\\n    total_log10 = (math.log10 total | math.round 3),\\n    total_log2 = (math.log 2 total | math.round 3),\\n    total_sqrt = (math.sqrt total | math.round 3),\\n    total_ln = (math.ln total | math.exp | math.round 2),\\n    total_cos = (math.cos total | math.acos | math.round 2),\\n    total_sin = (math.sin total | math.asin | math.round 2),\\n    total_tan = (math.tan total | math.atan | math.round 2),\\n    total_deg = (total | math.degrees | math.radians | math.round 2),\\n    total_square = (total | math.pow 2 | math.round 2),\\n    total_square_op = ((total ** 2) | math.round 2),\\n}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/math_module.prql\n---\nSELECT\n  ROUND(total, 2) AS total_original,\n  ABS(ROUND(PI() - total, 2)) AS total_x,\n  FLOOR(total) AS total_floor,\n  CEIL(total) AS total_ceil,\n  ROUND(LOG10(total), 3) AS total_log10,\n  ROUND(LOG10(total) / LOG10(2), 3) AS total_log2,\n  ROUND(SQRT(total), 3) AS total_sqrt,\n  ROUND(EXP(LN(total)), 2) AS total_ln,\n  ROUND(ACOS(COS(total)), 2) AS total_cos,\n  ROUND(ASIN(SIN(total)), 2) AS total_sin,\n  ROUND(ATAN(TAN(total)), 2) AS total_tan,\n  ROUND(RADIANS(DEGREES(total)), 2) AS total_deg,\n  ROUND(POW(total, 2), 2) AS total_square,\n  ROUND(POW(total, 2), 2) AS total_square_op\nFROM\n  invoices\nLIMIT\n  5\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__pipelines.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip (Only works on Sqlite implementations which have the extension\\n# installed\\n# https://stackoverflow.com/questions/24037982/how-to-use-regexp-in-sqlite)\\n\\nfrom tracks\\n\\nfilter (name ~= \\\"Love\\\")\\nfilter ((milliseconds / 1000 / 60) | in 3..4)\\nsort track_id\\ntake 1..15\\nselect {name, composer}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/pipelines.prql\n---\nWITH table_0 AS (\n  SELECT\n    name,\n    composer,\n    track_id\n  FROM\n    tracks\n  WHERE\n    REGEXP(name, 'Love')\n    AND milliseconds / 1000 / 60 BETWEEN 3 AND 4\n  ORDER BY\n    track_id\n  LIMIT\n    15\n)\nSELECT\n  name,\n  composer\nFROM\n  table_0\nORDER BY\n  track_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__read_csv.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip\\n# postgres:skip\\n# mysql:skip\\nfrom (read_csv \\\"data_file_root/media_types.csv\\\")\\nappend (read_json \\\"data_file_root/media_types.json\\\")\\nsort media_type_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/read_csv.prql\n---\nWITH table_0 AS (\n  SELECT\n    *\n  FROM\n    read_csv('data_file_root/media_types.csv')\n),\ntable_2 AS (\n  SELECT\n    *\n  FROM\n    table_0\n  UNION\n  ALL\n  SELECT\n    *\n  FROM\n    read_json('data_file_root/media_types.json')\n)\nSELECT\n  *\nFROM\n  table_2\nORDER BY\n  media_type_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__set_ops_remove.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nlet distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\\n\\nfrom_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2], [2], [3]] }'\\ndistinct\\nremove (from_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2]] }')\\nsort a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/set_ops_remove.prql\n---\nWITH table_0 AS (\n  SELECT\n    1 AS a\n  UNION\n  ALL\n  SELECT\n    2 AS a\n  UNION\n  ALL\n  SELECT\n    2 AS a\n  UNION\n  ALL\n  SELECT\n    3 AS a\n),\ntable_1 AS (\n  SELECT\n    1 AS a\n  UNION\n  ALL\n  SELECT\n    2 AS a\n),\ntable_2 AS (\n  SELECT\n    a\n  FROM\n    table_0\n  EXCEPT\n    DISTINCT\n  SELECT\n    *\n  FROM\n    table_1\n)\nSELECT\n  a\nFROM\n  table_2\nORDER BY\n  a\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom e=employees\\nfilter first_name != \\\"Mitchell\\\"\\nsort {first_name, last_name}\\n\\n# joining may use HashMerge, which can undo ORDER BY\\njoin manager=employees side:left (e.reports_to == manager.employee_id)\\n\\nselect {e.first_name, e.last_name, manager.first_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort.prql\n---\nWITH table_0 AS (\n  SELECT\n    first_name,\n    last_name,\n    reports_to\n  FROM\n    employees AS e\n  WHERE\n    first_name <> 'Mitchell'\n)\nSELECT\n  table_0.first_name,\n  table_0.last_name,\n  manager.first_name\nFROM\n  table_0\n  LEFT OUTER JOIN employees AS manager ON table_0.reports_to = manager.employee_id\nORDER BY\n  table_0.first_name,\n  table_0.last_name\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__sort_2.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from albums\\nselect { AA=album_id, artist_id }\\nsort AA\\nfilter AA >= 25\\njoin artists (==artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_2.prql\n---\nWITH table_1 AS (\n  SELECT\n    album_id AS \"AA\",\n    artist_id\n  FROM\n    albums\n),\ntable_0 AS (\n  SELECT\n    \"AA\",\n    artist_id\n  FROM\n    table_1\n  WHERE\n    \"AA\" >= 25\n)\nSELECT\n  table_0.\"AA\",\n  table_0.artist_id,\n  artists.*\nFROM\n  table_0\n  INNER JOIN artists ON table_0.artist_id = artists.artist_id\nORDER BY\n  table_0.\"AA\"\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__sort_3.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from [{track_id=0, album_id=1, genre_id=2}]\\nselect { AA=track_id, album_id, genre_id }\\nsort AA\\njoin side:left [{album_id=1, album_title=\\\"Songs\\\"}] (==album_id)\\nselect { AA, AT = album_title ?? \\\"unknown\\\", genre_id }\\nfilter AA < 25\\njoin side:left [{genre_id=1, genre_title=\\\"Rock\\\"}] (==genre_id)\\nselect { AA, AT, GT = genre_title ?? \\\"unknown\\\" }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_3.prql\n---\nWITH table_0 AS (\n  SELECT\n    0 AS track_id,\n    1 AS album_id,\n    2 AS genre_id\n),\ntable_5 AS (\n  SELECT\n    track_id AS \"AA\",\n    genre_id,\n    album_id\n  FROM\n    table_0\n),\ntable_1 AS (\n  SELECT\n    1 AS album_id,\n    'Songs' AS album_title\n),\ntable_4 AS (\n  SELECT\n    table_5.\"AA\",\n    COALESCE(table_1.album_title, 'unknown') AS \"AT\",\n    table_5.genre_id\n  FROM\n    table_5\n    LEFT OUTER JOIN table_1 ON table_5.album_id = table_1.album_id\n),\ntable_3 AS (\n  SELECT\n    \"AA\",\n    \"AT\",\n    genre_id\n  FROM\n    table_4\n  WHERE\n    \"AA\" < 25\n),\ntable_2 AS (\n  SELECT\n    1 AS genre_id,\n    'Rock' AS genre_title\n)\nSELECT\n  table_3.\"AA\",\n  table_3.\"AT\",\n  COALESCE(table_2.genre_title, 'unknown') AS \"GT\"\nFROM\n  table_3\n  LEFT OUTER JOIN table_2 ON table_3.genre_id = table_2.genre_id\nORDER BY\n  table_3.\"AA\"\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__switch.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# glaredb:skip (May be a bag of String type conversion for Postgres Client)\\n# mssql:test\\nfrom tracks\\nsort milliseconds\\nselect display = case [\\n    composer != null => composer,\\n    genre_id < 17 => 'no composer',\\n    true => f'unknown composer'\\n]\\ntake 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/switch.prql\n---\nWITH table_0 AS (\n  SELECT\n    CASE\n      WHEN composer IS NOT NULL THEN composer\n      WHEN genre_id < 17 THEN 'no composer'\n      ELSE 'unknown composer'\n    END AS display,\n    milliseconds\n  FROM\n    tracks\n  ORDER BY\n    milliseconds\n  LIMIT\n    10\n)\nSELECT\n  display\nFROM\n  table_0\nORDER BY\n  milliseconds\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {+track_id}\\ntake 3..5\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/take.prql\n---\nSELECT\n  *\nFROM\n  tracks\nORDER BY\n  track_id\nLIMIT\n  3 OFFSET 2\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__text_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# glaredb:skip — TODO: started raising an error on 2024-05-20; see `window.prql`\\n# for more details\\nfrom albums\\nselect {\\n    title,\\n    title_and_spaces = f\\\"  {title}  \\\",\\n    low = (title | text.lower),\\n    up = (title | text.upper),\\n    ltrimmed = (title | text.ltrim),\\n    rtrimmed = (title | text.rtrim),\\n    trimmed = (title | text.trim),\\n    len = (title | text.length),\\n    subs = (title | text.extract 2 5),\\n    replace = (title | text.replace \\\"al\\\" \\\"PIKA\\\"),\\n}\\nsort {title}\\nfilter (title | text.starts_with \\\"Black\\\") || (title | text.contains \\\"Sabbath\\\") || (title | text.ends_with \\\"os\\\")\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/text_module.prql\n---\nWITH table_0 AS (\n  SELECT\n    title,\n    CONCAT('  ', title, '  ') AS title_and_spaces,\n    LOWER(title) AS low,\n    UPPER(title) AS up,\n    LTRIM(title) AS ltrimmed,\n    RTRIM(title) AS rtrimmed,\n    TRIM(title) AS trimmed,\n    CHAR_LENGTH(title) AS len,\n    SUBSTRING(title, 2, 5) AS subs,\n    REPLACE(title, 'al', 'PIKA') AS \"replace\"\n  FROM\n    albums\n)\nSELECT\n  title,\n  title_and_spaces,\n  low,\n  up,\n  ltrimmed,\n  rtrimmed,\n  trimmed,\n  len,\n  subs,\n  \"replace\"\nFROM\n  table_0\nWHERE\n  title LIKE CONCAT('Black', '%')\n  OR title LIKE CONCAT('%', 'Sabbath', '%')\n  OR title LIKE CONCAT('%', 'os')\nORDER BY\n  title\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__window.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip problems with DISTINCT ON\\n# glaredb:skip — TODO: started raising an error on 2024-05-20, from https://github.com/PRQL/prql/actions/runs/9154902656/job/25198160283:\\n    # ERROR: This feature is not implemented: Unsupported ast node in sqltorel:\\n    # Substring { expr: Identifier(Ident { value: \\\"title\\\", quote_style: None }),\\n    # substring_from: Some(Value(Number(\\\"2\\\", false))), substring_for:\\n    # Some(Value(Number(\\\"5\\\", false))), special: true }\\nfrom tracks\\ngroup genre_id (\\n  sort milliseconds\\n  derive {\\n    num = row_number this,\\n    total = count this,\\n    last_val = last track_id,\\n  }\\n  take 10\\n)\\nsort {genre_id, milliseconds}\\nselect {track_id, genre_id, num, total, last_val}\\nfilter genre_id >= 22\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/window.prql\n---\nWITH table_0 AS (\n  SELECT\n    track_id,\n    genre_id,\n    ROW_NUMBER() OVER (\n      PARTITION BY genre_id\n      ORDER BY\n        milliseconds\n    ) AS num,\n    COUNT(*) OVER (\n      PARTITION BY genre_id\n      ORDER BY\n        milliseconds ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\n    ) AS total,\n    LAST_VALUE(track_id) OVER (\n      PARTITION BY genre_id\n      ORDER BY\n        milliseconds\n    ) AS last_val,\n    milliseconds,\n    ROW_NUMBER() OVER (\n      PARTITION BY genre_id\n      ORDER BY\n        milliseconds\n    ) AS _expr_0\n  FROM\n    tracks\n),\ntable_1 AS (\n  SELECT\n    track_id,\n    genre_id,\n    num,\n    total,\n    last_val,\n    milliseconds\n  FROM\n    table_0\n  WHERE\n    _expr_0 <= 10\n    AND genre_id >= 22\n)\nSELECT\n  track_id,\n  genre_id,\n  num,\n  total,\n  last_val\nFROM\n  table_1\nORDER BY\n  genre_id,\n  milliseconds\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__aggregation.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mysql:skip\\n# clickhouse:skip\\n# glaredb:skip (the string_agg function is not supported)\\nfrom tracks\\nfilter genre_id == 100\\nderive empty_name = name == ''\\naggregate {sum track_id, concat_array name, all empty_name, any empty_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/aggregation.prql\n---\n--- generic\n+++ sqlite\n@@ -1,9 +1,9 @@\n SELECT\n   COALESCE(SUM(track_id), 0),\n-  COALESCE(STRING_AGG(name, ''), ''),\n-  COALESCE(BOOL_AND(name = ''), TRUE),\n-  COALESCE(BOOL_OR(name = ''), FALSE)\n+  COALESCE(GROUP_CONCAT(name, ''), ''),\n+  COALESCE(MIN(name = '') > 0, TRUE),\n+  COALESCE(MAX(name = '') > 0, FALSE)\n FROM\n   tracks\n WHERE\n   genre_id = 100\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 10..15\\nappend (\\n  from invoices\\n  select { customer_id, invoice_id, billing_country }\\n  take 40..45\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select.prql\nsnapshot_kind: text\n---\n--- generic\n+++ postgres\n@@ -1,26 +1,19 @@\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      billing_country,\n-      invoice_id\n-    FROM\n-      invoices\n-    LIMIT\n-      6 OFFSET 9\n-  ) AS table_2\n+(\n+  SELECT\n+    billing_country,\n+    invoice_id\n+  FROM\n+    invoices\n+  LIMIT\n+    6 OFFSET 9\n+)\n UNION\n-ALL\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      billing_country,\n-      invoice_id\n-    FROM\n-      invoices\n-    LIMIT\n-      6 OFFSET 39\n-  ) AS table_3\n+ALL (\n+  SELECT\n+    billing_country,\n+    invoice_id\n+  FROM\n+    invoices\n+  LIMIT\n+    6 OFFSET 39\n+)\n\n--- generic\n+++ redshift\n@@ -1,26 +1,19 @@\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      billing_country,\n-      invoice_id\n-    FROM\n-      invoices\n-    LIMIT\n-      6 OFFSET 9\n-  ) AS table_2\n+(\n+  SELECT\n+    billing_country,\n+    invoice_id\n+  FROM\n+    invoices\n+  LIMIT\n+    6 OFFSET 9\n+)\n UNION\n-ALL\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      billing_country,\n-      invoice_id\n-    FROM\n-      invoices\n-    LIMIT\n-      6 OFFSET 39\n-  ) AS table_3\n+ALL (\n+  SELECT\n+    billing_country,\n+    invoice_id\n+  FROM\n+    invoices\n+  LIMIT\n+    6 OFFSET 39\n+)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_compute.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nderive total = case [total < 10 => total * 2, true => total]\\nselect { customer_id, invoice_id, total }\\ntake 5\\nappend (\\n  from invoice_items\\n  derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\\n  select { invoice_line_id, invoice_id, unit_price }\\n  take 5\\n)\\nselect { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_compute.prql\nsnapshot_kind: text\n---\n--- generic\n+++ glaredb\n@@ -29,13 +29,13 @@\n         END AS unit_price,\n         invoice_line_id\n       FROM\n         invoice_items\n       LIMIT\n         5\n     ) AS table_4\n )\n SELECT\n   customer_id * 2 AS a,\n-  ROUND(invoice_id * _expr_0, 1) AS b\n+  ROUND((invoice_id * _expr_0)::numeric, 1) AS b\n FROM\n   table_1\n\n\n--- generic\n+++ postgres\n@@ -1,41 +1,34 @@\n WITH table_1 AS (\n-  SELECT\n-    *\n-  FROM\n-    (\n-      SELECT\n-        invoice_id,\n-        CASE\n-          WHEN total < 10 THEN total * 2\n-          ELSE total\n-        END AS _expr_0,\n-        customer_id\n-      FROM\n-        invoices\n-      LIMIT\n-        5\n-    ) AS table_3\n+  (\n+    SELECT\n+      invoice_id,\n+      CASE\n+        WHEN total < 10 THEN total * 2\n+        ELSE total\n+      END AS _expr_0,\n+      customer_id\n+    FROM\n+      invoices\n+    LIMIT\n+      5\n+  )\n   UNION\n-  ALL\n-  SELECT\n-    *\n-  FROM\n-    (\n-      SELECT\n-        invoice_id,\n-        CASE\n-          WHEN unit_price < 1 THEN unit_price * 2\n-          ELSE unit_price\n-        END AS unit_price,\n-        invoice_line_id\n-      FROM\n-        invoice_items\n-      LIMIT\n-        5\n-    ) AS table_4\n+  ALL (\n+    SELECT\n+      invoice_id,\n+      CASE\n+        WHEN unit_price < 1 THEN unit_price * 2\n+        ELSE unit_price\n+      END AS unit_price,\n+      invoice_line_id\n+    FROM\n+      invoice_items\n+    LIMIT\n+      5\n+  )\n )\n SELECT\n   customer_id * 2 AS a,\n-  ROUND(invoice_id * _expr_0, 1) AS b\n+  ROUND((invoice_id * _expr_0)::numeric, 1) AS b\n FROM\n   table_1\n\n--- generic\n+++ redshift\n@@ -1,41 +1,34 @@\n WITH table_1 AS (\n-  SELECT\n-    *\n-  FROM\n-    (\n-      SELECT\n-        invoice_id,\n-        CASE\n-          WHEN total < 10 THEN total * 2\n-          ELSE total\n-        END AS _expr_0,\n-        customer_id\n-      FROM\n-        invoices\n-      LIMIT\n-        5\n-    ) AS table_3\n+  (\n+    SELECT\n+      invoice_id,\n+      CASE\n+        WHEN total < 10 THEN total * 2\n+        ELSE total\n+      END AS _expr_0,\n+      customer_id\n+    FROM\n+      invoices\n+    LIMIT\n+      5\n+  )\n   UNION\n-  ALL\n-  SELECT\n-    *\n-  FROM\n-    (\n-      SELECT\n-        invoice_id,\n-        CASE\n-          WHEN unit_price < 1 THEN unit_price * 2\n-          ELSE unit_price\n-        END AS unit_price,\n-        invoice_line_id\n-      FROM\n-        invoice_items\n-      LIMIT\n-        5\n-    ) AS table_4\n+  ALL (\n+    SELECT\n+      invoice_id,\n+      CASE\n+        WHEN unit_price < 1 THEN unit_price * 2\n+        ELSE unit_price\n+      END AS unit_price,\n+      invoice_line_id\n+    FROM\n+      invoice_items\n+    LIMIT\n+      5\n+  )\n )\n SELECT\n   customer_id * 2 AS a,\n   ROUND(invoice_id * _expr_0, 1) AS b\n FROM\n   table_1\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_multiple_with_null.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 5\\nappend (\\n  from employees\\n  select { employee_id, employee_id, country }\\n  take 5\\n)\\nappend (\\n  from invoice_items\\n  select { invoice_line_id, invoice_id, null }\\n  take 5\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql\nsnapshot_kind: text\n---\n--- generic\n+++ postgres\n@@ -1,40 +1,29 @@\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      billing_country,\n-      invoice_id\n-    FROM\n-      invoices\n-    LIMIT\n-      5\n-  ) AS table_4\n+(\n+  SELECT\n+    billing_country,\n+    invoice_id\n+  FROM\n+    invoices\n+  LIMIT\n+    5\n+)\n UNION\n-ALL\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      country,\n-      employee_id\n-    FROM\n-      employees\n-    LIMIT\n-      5\n-  ) AS table_5\n+ALL (\n+  SELECT\n+    country,\n+    employee_id\n+  FROM\n+    employees\n+  LIMIT\n+    5\n+)\n UNION\n-ALL\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      NULL,\n-      invoice_id\n-    FROM\n-      invoice_items\n-    LIMIT\n-      5\n-  ) AS table_6\n+ALL (\n+  SELECT\n+    NULL,\n+    invoice_id\n+  FROM\n+    invoice_items\n+  LIMIT\n+    5\n+)\n\n--- generic\n+++ redshift\n@@ -1,40 +1,29 @@\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      billing_country,\n-      invoice_id\n-    FROM\n-      invoices\n-    LIMIT\n-      5\n-  ) AS table_4\n+(\n+  SELECT\n+    billing_country,\n+    invoice_id\n+  FROM\n+    invoices\n+  LIMIT\n+    5\n+)\n UNION\n-ALL\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      country,\n-      employee_id\n-    FROM\n-      employees\n-    LIMIT\n-      5\n-  ) AS table_5\n+ALL (\n+  SELECT\n+    country,\n+    employee_id\n+  FROM\n+    employees\n+  LIMIT\n+    5\n+)\n UNION\n-ALL\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      NULL,\n-      invoice_id\n-    FROM\n-      invoice_items\n-    LIMIT\n-      5\n-  ) AS table_6\n+ALL (\n+  SELECT\n+    NULL,\n+    invoice_id\n+  FROM\n+    invoice_items\n+  LIMIT\n+    5\n+)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_nulls.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect {an_id = invoice_id, name = null}\\ntake 2\\nappend (\\n  from employees\\n  select {an_id = null, name = first_name}\\n  take 2\\n)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_nulls.prql\n---\n--- generic\n+++ postgres\n@@ -1,26 +1,19 @@\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      invoice_id AS an_id,\n-      NULL AS name\n-    FROM\n-      invoices\n-    LIMIT\n-      2\n-  ) AS table_2\n+(\n+  SELECT\n+    invoice_id AS an_id,\n+    NULL AS name\n+  FROM\n+    invoices\n+  LIMIT\n+    2\n+)\n UNION\n-ALL\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      NULL AS an_id,\n-      first_name AS name\n-    FROM\n-      employees\n-    LIMIT\n-      2\n-  ) AS table_3\n+ALL (\n+  SELECT\n+    NULL AS an_id,\n+    first_name AS name\n+  FROM\n+    employees\n+  LIMIT\n+    2\n+)\n\n--- generic\n+++ redshift\n@@ -1,26 +1,19 @@\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      invoice_id AS an_id,\n-      NULL AS name\n-    FROM\n-      invoices\n-    LIMIT\n-      2\n-  ) AS table_2\n+(\n+  SELECT\n+    invoice_id AS an_id,\n+    NULL AS name\n+  FROM\n+    invoices\n+  LIMIT\n+    2\n+)\n UNION\n-ALL\n-SELECT\n-  *\n-FROM\n-  (\n-    SELECT\n-      NULL AS an_id,\n-      first_name AS name\n-    FROM\n-      employees\n-    LIMIT\n-      2\n-  ) AS table_3\n+ALL (\n+  SELECT\n+    NULL AS an_id,\n+    first_name AS name\n+  FROM\n+    employees\n+  LIMIT\n+    2\n+)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_simple.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { invoice_id, billing_country }\\nappend (\\n  from invoices\\n  select { invoice_id = `invoice_id` + 100, billing_country }\\n)\\nfilter (billing_country | text.starts_with(\\\"I\\\"))\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_simple.prql\n---\n--- generic\n+++ sqlite\n@@ -11,11 +11,11 @@\n     billing_country\n   FROM\n     invoices\n )\n SELECT\n   invoice_id,\n   billing_country\n FROM\n   table_1\n WHERE\n-  billing_country LIKE CONCAT('I', '%')\n+  billing_country LIKE 'I' || '%'\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__arithmetic.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom [\\n    { id = 1, x_int =  13, x_float =  13.0, k_int =  5, k_float =  5.0 },\\n    { id = 2, x_int = -13, x_float = -13.0, k_int =  5, k_float =  5.0 },\\n    { id = 3, x_int =  13, x_float =  13.0, k_int = -5, k_float = -5.0 },\\n    { id = 4, x_int = -13, x_float = -13.0, k_int = -5, k_float = -5.0 },\\n]\\nselect {\\n    id,\\n\\n    x_int / k_int,\\n    x_int / k_float,\\n    x_float / k_int,\\n    x_float / k_float,\\n\\n    q_ii = x_int // k_int,\\n    q_if = x_int // k_float,\\n    q_fi = x_float // k_int,\\n    q_ff = x_float // k_float,\\n\\n    r_ii = x_int % k_int,\\n    r_if = x_int % k_float,\\n    r_fi = x_float % k_int,\\n    r_ff = x_float % k_float,\\n\\n    (q_ii * k_int + r_ii | math.round 0),\\n    (q_if * k_float + r_if | math.round 0),\\n    (q_fi * k_int + r_fi | math.round 0),\\n    (q_ff * k_float + r_ff | math.round 0),\\n}\\nsort id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/arithmetic.prql\nsnapshot_kind: text\n---\n--- generic\n+++ clickhouse\n@@ -25,42 +25,36 @@\n   ALL\n   SELECT\n     4 AS id,\n     -13 AS x_int,\n     -13.0 AS x_float,\n     -5 AS k_int,\n     -5.0 AS k_float\n )\n SELECT\n   id,\n-  x_int / k_int,\n-  x_int / k_float,\n-  x_float / k_int,\n-  x_float / k_float,\n-  FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n-  FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) AS q_if,\n-  FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n-  FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n+  (x_int / k_int),\n+  (x_int / k_float),\n+  (x_float / k_int),\n+  (x_float / k_float),\n+  (x_int DIV k_int) AS q_ii,\n+  (x_int DIV k_float) AS q_if,\n+  (x_float DIV k_int) AS q_fi,\n+  (x_float DIV k_float) AS q_ff,\n   x_int % k_int AS r_ii,\n   x_int % k_float AS r_if,\n   x_float % k_int AS r_fi,\n   x_float % k_float AS r_ff,\n-  ROUND(\n-    FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n-    0\n-  ),\n-  ROUND(\n-    FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) * k_float + x_int % k_float,\n-    0\n-  ),\n+  ROUND((x_int DIV k_int) * k_int + x_int % k_int, 0),\n   ROUND(\n-    FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) * k_int + x_float % k_int,\n+    (x_int DIV k_float) * k_float + x_int % k_float,\n     0\n   ),\n+  ROUND((x_float DIV k_int) * k_int + x_float % k_int, 0),\n   ROUND(\n-    FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) * k_float + x_float % k_float,\n+    (x_float DIV k_float) * k_float + x_float % k_float,\n     0\n   )\n FROM\n   table_0\n ORDER BY\n   id\n\n--- generic\n+++ duckdb\n@@ -25,42 +25,39 @@\n   ALL\n   SELECT\n     4 AS id,\n     -13 AS x_int,\n     -13.0 AS x_float,\n     -5 AS k_int,\n     -5.0 AS k_float\n )\n SELECT\n   id,\n-  x_int / k_int,\n-  x_int / k_float,\n-  x_float / k_int,\n-  x_float / k_float,\n-  FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n-  FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) AS q_if,\n-  FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n-  FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n+  (x_int / k_int),\n+  (x_int / k_float),\n+  (x_float / k_int),\n+  (x_float / k_float),\n+  TRUNC(x_int / k_int) AS q_ii,\n+  TRUNC(x_int / k_float) AS q_if,\n+  TRUNC(x_float / k_int) AS q_fi,\n+  TRUNC(x_float / k_float) AS q_ff,\n   x_int % k_int AS r_ii,\n   x_int % k_float AS r_if,\n   x_float % k_int AS r_fi,\n   x_float % k_float AS r_ff,\n-  ROUND(\n-    FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n-    0\n-  ),\n+  ROUND(TRUNC(x_int / k_int) * k_int + x_int % k_int, 0),\n   ROUND(\n-    FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) * k_float + x_int % k_float,\n+    TRUNC(x_int / k_float) * k_float + x_int % k_float,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) * k_int + x_float % k_int,\n+    TRUNC(x_float / k_int) * k_int + x_float % k_int,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) * k_float + x_float % k_float,\n+    TRUNC(x_float / k_float) * k_float + x_float % k_float,\n     0\n   )\n FROM\n   table_0\n ORDER BY\n   id\n\n\n--- generic\n+++ glaredb\n@@ -25,42 +25,46 @@\n   ALL\n   SELECT\n     4 AS id,\n     -13 AS x_int,\n     -13.0 AS x_float,\n     -5 AS k_int,\n     -5.0 AS k_float\n )\n SELECT\n   id,\n-  x_int / k_int,\n-  x_int / k_float,\n-  x_float / k_int,\n-  x_float / k_float,\n-  FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n-  FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) AS q_if,\n-  FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n-  FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n+  (x_int * 1.0 / k_int),\n+  (x_int * 1.0 / k_float),\n+  (x_float * 1.0 / k_int),\n+  (x_float * 1.0 / k_float),\n+  TRUNC(x_int / k_int) AS q_ii,\n+  TRUNC(x_int / k_float) AS q_if,\n+  TRUNC(x_float / k_int) AS q_fi,\n+  TRUNC(x_float / k_float) AS q_ff,\n   x_int % k_int AS r_ii,\n   x_int % k_float AS r_if,\n   x_float % k_int AS r_fi,\n   x_float % k_float AS r_ff,\n   ROUND(\n-    FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n+    (TRUNC(x_int / k_int) * k_int + x_int % k_int)::numeric,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) * k_float + x_int % k_float,\n+    (\n+      TRUNC(x_int / k_float) * k_float + x_int % k_float\n+    )::numeric,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) * k_int + x_float % k_int,\n+    (TRUNC(x_float / k_int) * k_int + x_float % k_int)::numeric,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) * k_float + x_float % k_float,\n+    (\n+      TRUNC(x_float / k_float) * k_float + x_float % k_float\n+    )::numeric,\n     0\n   )\n FROM\n   table_0\n ORDER BY\n   id\n\n--- generic\n+++ mssql\n@@ -25,24 +25,24 @@\n   ALL\n   SELECT\n     4 AS id,\n     -13 AS x_int,\n     -13.0 AS x_float,\n     -5 AS k_int,\n     -5.0 AS k_float\n )\n SELECT\n   id,\n-  x_int / k_int,\n-  x_int / k_float,\n-  x_float / k_int,\n-  x_float / k_float,\n+  (x_int * 1.0 / k_int),\n+  (x_int * 1.0 / k_float),\n+  (x_float * 1.0 / k_int),\n+  (x_float * 1.0 / k_float),\n   FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n   FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) AS q_if,\n   FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n   FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n   x_int % k_int AS r_ii,\n   x_int % k_float AS r_if,\n   x_float % k_int AS r_fi,\n   x_float % k_float AS r_ff,\n   ROUND(\n     FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n\n--- generic\n+++ mysql\n@@ -25,42 +25,42 @@\n   ALL\n   SELECT\n     4 AS id,\n     -13 AS x_int,\n     -13.0 AS x_float,\n     -5 AS k_int,\n     -5.0 AS k_float\n )\n SELECT\n   id,\n-  x_int / k_int,\n-  x_int / k_float,\n-  x_float / k_int,\n-  x_float / k_float,\n-  FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n-  FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) AS q_if,\n-  FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n-  FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n-  x_int % k_int AS r_ii,\n-  x_int % k_float AS r_if,\n-  x_float % k_int AS r_fi,\n-  x_float % k_float AS r_ff,\n+  (x_int / k_int),\n+  (x_int / k_float),\n+  (x_float / k_int),\n+  (x_float / k_float),\n+  (x_int DIV k_int) AS q_ii,\n+  (x_int DIV k_float) AS q_if,\n+  (x_float DIV k_int) AS q_fi,\n+  (x_float DIV k_float) AS q_ff,\n+  ROUND(MOD(x_int, k_int)) AS r_ii,\n+  ROUND(MOD(x_int, k_float)) AS r_if,\n+  ROUND(MOD(x_float, k_int)) AS r_fi,\n+  ROUND(MOD(x_float, k_float)) AS r_ff,\n   ROUND(\n-    FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n+    (x_int DIV k_int) * k_int + ROUND(MOD(x_int, k_int)),\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) * k_float + x_int % k_float,\n+    (x_int DIV k_float) * k_float + ROUND(MOD(x_int, k_float)),\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) * k_int + x_float % k_int,\n+    (x_float DIV k_int) * k_int + ROUND(MOD(x_float, k_int)),\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) * k_float + x_float % k_float,\n+    (x_float DIV k_float) * k_float + ROUND(MOD(x_float, k_float)),\n     0\n   )\n FROM\n   table_0\n ORDER BY\n   id\n\n--- generic\n+++ postgres\n@@ -25,42 +25,46 @@\n   ALL\n   SELECT\n     4 AS id,\n     -13 AS x_int,\n     -13.0 AS x_float,\n     -5 AS k_int,\n     -5.0 AS k_float\n )\n SELECT\n   id,\n-  x_int / k_int,\n-  x_int / k_float,\n-  x_float / k_int,\n-  x_float / k_float,\n-  FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n-  FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) AS q_if,\n-  FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n-  FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n+  (x_int * 1.0 / k_int),\n+  (x_int * 1.0 / k_float),\n+  (x_float * 1.0 / k_int),\n+  (x_float * 1.0 / k_float),\n+  TRUNC(x_int / k_int) AS q_ii,\n+  TRUNC(x_int / k_float) AS q_if,\n+  TRUNC(x_float / k_int) AS q_fi,\n+  TRUNC(x_float / k_float) AS q_ff,\n   x_int % k_int AS r_ii,\n   x_int % k_float AS r_if,\n   x_float % k_int AS r_fi,\n   x_float % k_float AS r_ff,\n   ROUND(\n-    FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n+    (TRUNC(x_int / k_int) * k_int + x_int % k_int)::numeric,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) * k_float + x_int % k_float,\n+    (\n+      TRUNC(x_int / k_float) * k_float + x_int % k_float\n+    )::numeric,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) * k_int + x_float % k_int,\n+    (TRUNC(x_float / k_int) * k_int + x_float % k_int)::numeric,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) * k_float + x_float % k_float,\n+    (\n+      TRUNC(x_float / k_float) * k_float + x_float % k_float\n+    )::numeric,\n     0\n   )\n FROM\n   table_0\n ORDER BY\n   id\n\n--- generic\n+++ redshift\n@@ -25,24 +25,24 @@\n   ALL\n   SELECT\n     4 AS id,\n     -13 AS x_int,\n     -13.0 AS x_float,\n     -5 AS k_int,\n     -5.0 AS k_float\n )\n SELECT\n   id,\n-  x_int / k_int,\n-  x_int / k_float,\n-  x_float / k_int,\n-  x_float / k_float,\n+  (x_int * 1.0 / k_int),\n+  (x_int * 1.0 / k_float),\n+  (x_float * 1.0 / k_int),\n+  (x_float * 1.0 / k_float),\n   FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n   FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) AS q_if,\n   FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n   FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n   x_int % k_int AS r_ii,\n   x_int % k_float AS r_if,\n   x_float % k_int AS r_fi,\n   x_float % k_float AS r_ff,\n   ROUND(\n     FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n\n--- generic\n+++ sqlite\n@@ -25,42 +25,42 @@\n   ALL\n   SELECT\n     4 AS id,\n     -13 AS x_int,\n     -13.0 AS x_float,\n     -5 AS k_int,\n     -5.0 AS k_float\n )\n SELECT\n   id,\n-  x_int / k_int,\n-  x_int / k_float,\n-  x_float / k_int,\n-  x_float / k_float,\n-  FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n-  FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) AS q_if,\n-  FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n-  FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n+  (x_int * 1.0 / k_int),\n+  (x_int * 1.0 / k_float),\n+  (x_float * 1.0 / k_int),\n+  (x_float * 1.0 / k_float),\n+  ROUND(ABS(x_int / k_int) - 0.5) * SIGN(x_int) * SIGN(k_int) AS q_ii,\n+  ROUND(ABS(x_int / k_float) - 0.5) * SIGN(x_int) * SIGN(k_float) AS q_if,\n+  ROUND(ABS(x_float / k_int) - 0.5) * SIGN(x_float) * SIGN(k_int) AS q_fi,\n+  ROUND(ABS(x_float / k_float) - 0.5) * SIGN(x_float) * SIGN(k_float) AS q_ff,\n   x_int % k_int AS r_ii,\n   x_int % k_float AS r_if,\n   x_float % k_int AS r_fi,\n   x_float % k_float AS r_ff,\n   ROUND(\n-    FLOOR(ABS(x_int / k_int)) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n+    ROUND(ABS(x_int / k_int) - 0.5) * SIGN(x_int) * SIGN(k_int) * k_int + x_int % k_int,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_int / k_float)) * SIGN(x_int) * SIGN(k_float) * k_float + x_int % k_float,\n+    ROUND(ABS(x_int / k_float) - 0.5) * SIGN(x_int) * SIGN(k_float) * k_float + x_int % k_float,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_int)) * SIGN(x_float) * SIGN(k_int) * k_int + x_float % k_int,\n+    ROUND(ABS(x_float / k_int) - 0.5) * SIGN(x_float) * SIGN(k_int) * k_int + x_float % k_int,\n     0\n   ),\n   ROUND(\n-    FLOOR(ABS(x_float / k_float)) * SIGN(x_float) * SIGN(k_float) * k_float + x_float % k_float,\n+    ROUND(ABS(x_float / k_float) - 0.5) * SIGN(x_float) * SIGN(k_float) * k_float + x_float % k_float,\n     0\n   )\n FROM\n   table_0\n ORDER BY\n   id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__cast.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {-bytes}\\nselect {\\n    name,\\n    bin = ((album_id | as REAL) * 99)\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/cast.prql\n---\n--- generic\n+++ mssql\n@@ -1,19 +1,19 @@\n WITH table_0 AS (\n   SELECT\n     name,\n     CAST(album_id AS REAL) * 99 AS bin,\n     bytes\n   FROM\n     tracks\n   ORDER BY\n-    bytes DESC\n-  LIMIT\n-    20\n+    bytes DESC OFFSET 0 ROWS\n+  FETCH FIRST\n+    20 ROWS ONLY\n )\n SELECT\n   name,\n   bin\n FROM\n   table_0\n ORDER BY\n   bytes DESC\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__constants_only.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from genres\\ntake 10\\nfilter true\\ntake 20\\nfilter true\\nselect d = 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/constants_only.prql\n---\n--- generic\n+++ postgres\n@@ -1,20 +1,18 @@\n WITH table_1 AS (\n   SELECT\n-    NULL\n   FROM\n     genres\n   LIMIT\n     10\n ), table_0 AS (\n   SELECT\n-    NULL\n   FROM\n     table_1\n   WHERE\n     true\n   LIMIT\n     20\n )\n SELECT\n   10 AS d\n FROM\n\n--- generic\n+++ redshift\n@@ -1,20 +1,18 @@\n WITH table_1 AS (\n   SELECT\n-    NULL\n   FROM\n     genres\n   LIMIT\n     10\n ), table_0 AS (\n   SELECT\n-    NULL\n   FROM\n     table_1\n   WHERE\n     true\n   LIMIT\n     20\n )\n SELECT\n   10 AS d\n FROM\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__distinct.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {album_id, genre_id}\\ngroup tracks.* (take 1)\\nsort tracks.*\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct.prql\n---\n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__distinct_on.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {genre_id, media_type_id, album_id}\\ngroup {genre_id, media_type_id} (sort {-album_id} | take 1)\\nsort {-genre_id, media_type_id}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct_on.prql\n---\n--- generic\n+++ clickhouse\n@@ -1,25 +1,21 @@\n WITH table_0 AS (\n   SELECT\n-    genre_id,\n+    DISTINCT ON (genre_id, media_type_id) genre_id,\n     media_type_id,\n-    album_id,\n-    ROW_NUMBER() OVER (\n-      PARTITION BY genre_id,\n-      media_type_id\n-      ORDER BY\n-        album_id DESC\n-    ) AS _expr_0\n+    album_id\n   FROM\n     tracks\n+  ORDER BY\n+    genre_id,\n+    media_type_id,\n+    album_id DESC\n )\n SELECT\n   genre_id,\n   media_type_id,\n   album_id\n FROM\n   table_0\n-WHERE\n-  _expr_0 <= 1\n ORDER BY\n   genre_id DESC,\n   media_type_id\n\n--- generic\n+++ duckdb\n@@ -1,25 +1,21 @@\n WITH table_0 AS (\n   SELECT\n-    genre_id,\n+    DISTINCT ON (genre_id, media_type_id) genre_id,\n     media_type_id,\n-    album_id,\n-    ROW_NUMBER() OVER (\n-      PARTITION BY genre_id,\n-      media_type_id\n-      ORDER BY\n-        album_id DESC\n-    ) AS _expr_0\n+    album_id\n   FROM\n     tracks\n+  ORDER BY\n+    genre_id,\n+    media_type_id,\n+    album_id DESC\n )\n SELECT\n   genre_id,\n   media_type_id,\n   album_id\n FROM\n   table_0\n-WHERE\n-  _expr_0 <= 1\n ORDER BY\n   genre_id DESC,\n   media_type_id\n\n\n\n\n\n--- generic\n+++ postgres\n@@ -1,25 +1,21 @@\n WITH table_0 AS (\n   SELECT\n-    genre_id,\n+    DISTINCT ON (genre_id, media_type_id) genre_id,\n     media_type_id,\n-    album_id,\n-    ROW_NUMBER() OVER (\n-      PARTITION BY genre_id,\n-      media_type_id\n-      ORDER BY\n-        album_id DESC\n-    ) AS _expr_0\n+    album_id\n   FROM\n     tracks\n+  ORDER BY\n+    genre_id,\n+    media_type_id,\n+    album_id DESC\n )\n SELECT\n   genre_id,\n   media_type_id,\n   album_id\n FROM\n   table_0\n-WHERE\n-  _expr_0 <= 1\n ORDER BY\n   genre_id DESC,\n   media_type_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__genre_counts.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (ClickHouse prefers aliases to column names https://github.com/PRQL/prql/issues/2827)\\n# mssql:test\\nlet genre_count = (\\n    from genres\\n    aggregate {a = count name}\\n)\\n\\nfrom genre_count\\nfilter a > 0\\nselect a = -a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/genre_counts.prql\n---\n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_all.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom a=albums\\ntake 10\\njoin tracks (==album_id)\\ngroup {a.album_id, a.title} (aggregate price = (sum tracks.unit_price | math.round 2))\\nsort album_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_all.prql\n---\n--- generic\n+++ glaredb\n@@ -3,19 +3,22 @@\n     album_id,\n     title\n   FROM\n     albums AS a\n   LIMIT\n     10\n )\n SELECT\n   table_0.album_id,\n   table_0.title,\n-  ROUND(COALESCE(SUM(tracks.unit_price), 0), 2) AS price\n+  ROUND(\n+    (COALESCE(SUM(tracks.unit_price), 0))::numeric,\n+    2\n+  ) AS price\n FROM\n   table_0\n   INNER JOIN tracks ON table_0.album_id = tracks.album_id\n GROUP BY\n   table_0.album_id,\n   table_0.title\n ORDER BY\n   table_0.album_id\n\n--- generic\n+++ mssql\n@@ -1,18 +1,23 @@\n WITH table_0 AS (\n   SELECT\n     album_id,\n     title\n   FROM\n     albums AS a\n-  LIMIT\n-    10\n+  ORDER BY\n+    (\n+      SELECT\n+        NULL\n+    ) OFFSET 0 ROWS\n+  FETCH FIRST\n+    10 ROWS ONLY\n )\n SELECT\n   table_0.album_id,\n   table_0.title,\n   ROUND(COALESCE(SUM(tracks.unit_price), 0), 2) AS price\n FROM\n   table_0\n   INNER JOIN tracks ON table_0.album_id = tracks.album_id\n GROUP BY\n   table_0.album_id,\n\n\n--- generic\n+++ postgres\n@@ -3,19 +3,22 @@\n     album_id,\n     title\n   FROM\n     albums AS a\n   LIMIT\n     10\n )\n SELECT\n   table_0.album_id,\n   table_0.title,\n-  ROUND(COALESCE(SUM(tracks.unit_price), 0), 2) AS price\n+  ROUND(\n+    (COALESCE(SUM(tracks.unit_price), 0))::numeric,\n+    2\n+  ) AS price\n FROM\n   table_0\n   INNER JOIN tracks ON table_0.album_id = tracks.album_id\n GROUP BY\n   table_0.album_id,\n   table_0.title\n ORDER BY\n   table_0.album_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nderive d = album_id + 1\\ngroup d (\\n    aggregate {\\n        n1 = (track_id | sum),\\n    }\\n)\\nsort d\\ntake 10\\nselect { d1 = d, n1 }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort.prql\n---\n--- generic\n+++ mssql\n@@ -8,21 +8,21 @@\n     album_id + 1\n ),\n table_1 AS (\n   SELECT\n     _expr_0 AS d1,\n     n1,\n     _expr_0\n   FROM\n     table_0\n   ORDER BY\n-    _expr_0\n-  LIMIT\n-    10\n+    _expr_0 OFFSET 0 ROWS\n+  FETCH FIRST\n+    10 ROWS ONLY\n )\n SELECT\n   d1,\n   n1\n FROM\n   table_1\n ORDER BY\n   d1\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_sort_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_derive_select_join.prql\n---\n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_sort_filter_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nfilter (this.album_title_count) > 10\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_filter_derive_select_join.prql\n---\n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_sort_limit_take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# Compute the 3 longest songs for each genre and sort by genre\\n# mssql:test\\nfrom tracks\\nselect {genre_id,milliseconds}\\ngroup {genre_id} (\\n  sort {-milliseconds}\\n  take 3\\n)\\njoin genres (==genre_id)\\nselect {name, milliseconds}\\nsort {+name,-milliseconds}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_limit_take.prql\n---\n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__invoice_totals.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (clickhouse doesn't have lag function)\\n\\n#! Calculate a number of metrics about the sales of tracks in each city.\\nfrom i=invoices\\njoin ii=invoice_items (==invoice_id)\\nderive {\\n    city = i.billing_city,\\n    street = i.billing_address,\\n}\\ngroup {city, street} (\\n    derive total = ii.unit_price * ii.quantity\\n    aggregate {\\n        num_orders = count_distinct i.invoice_id,\\n        num_tracks = sum ii.quantity,\\n        total_price = sum total,\\n    }\\n)\\ngroup {city} (\\n    sort street\\n    window expanding:true (\\n        derive {running_total_num_tracks = sum num_tracks}\\n    )\\n)\\nsort {city, street}\\nderive {num_tracks_last_week = lag 7 num_tracks}\\nselect {\\n    city,\\n    street,\\n    num_orders,\\n    num_tracks,\\n    running_total_num_tracks,\\n    num_tracks_last_week\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/invoice_totals.prql\n---\n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__loop_01.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (DB::Exception: Syntax error)\\n# glaredb:skip (DataFusion does not support recursive CTEs https://github.com/apache/arrow-datafusion/issues/462)\\nfrom [{n = 1}]\\nselect n = n - 2\\nloop (filter n < 4 | select n = n + 1)\\nselect n = n * 2\\nsort n\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/loop_01.prql\n---\n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__math_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# sqlite:skip (see https://github.com/rusqlite/rusqlite/issues/1211)\\nfrom invoices\\ntake 5\\nselect {\\n    total_original = (total | math.round 2),\\n    total_x = (math.pi - total | math.round 2 | math.abs),\\n    total_floor = (math.floor total),\\n    total_ceil = (math.ceil total),\\n    total_log10 = (math.log10 total | math.round 3),\\n    total_log2 = (math.log 2 total | math.round 3),\\n    total_sqrt = (math.sqrt total | math.round 3),\\n    total_ln = (math.ln total | math.exp | math.round 2),\\n    total_cos = (math.cos total | math.acos | math.round 2),\\n    total_sin = (math.sin total | math.asin | math.round 2),\\n    total_tan = (math.tan total | math.atan | math.round 2),\\n    total_deg = (total | math.degrees | math.radians | math.round 2),\\n    total_square = (total | math.pow 2 | math.round 2),\\n    total_square_op = ((total ** 2) | math.round 2),\\n}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/math_module.prql\n---\n--- generic\n+++ glaredb\n@@ -1,19 +1,19 @@\n SELECT\n-  ROUND(total, 2) AS total_original,\n-  ABS(ROUND(PI() - total, 2)) AS total_x,\n+  ROUND((total)::numeric, 2) AS total_original,\n+  ABS(ROUND((PI() - total)::numeric, 2)) AS total_x,\n   FLOOR(total) AS total_floor,\n   CEIL(total) AS total_ceil,\n-  ROUND(LOG10(total), 3) AS total_log10,\n-  ROUND(LOG10(total) / LOG10(2), 3) AS total_log2,\n-  ROUND(SQRT(total), 3) AS total_sqrt,\n-  ROUND(EXP(LN(total)), 2) AS total_ln,\n-  ROUND(ACOS(COS(total)), 2) AS total_cos,\n-  ROUND(ASIN(SIN(total)), 2) AS total_sin,\n-  ROUND(ATAN(TAN(total)), 2) AS total_tan,\n-  ROUND(RADIANS(DEGREES(total)), 2) AS total_deg,\n-  ROUND(POW(total, 2), 2) AS total_square,\n-  ROUND(POW(total, 2), 2) AS total_square_op\n+  ROUND((LOG10(total))::numeric, 3) AS total_log10,\n+  ROUND((LOG10(total) / LOG10(2))::numeric, 3) AS total_log2,\n+  ROUND((SQRT(total))::numeric, 3) AS total_sqrt,\n+  ROUND((EXP(LN(total)))::numeric, 2) AS total_ln,\n+  ROUND((ACOS(COS(total)))::numeric, 2) AS total_cos,\n+  ROUND((ASIN(SIN(total)))::numeric, 2) AS total_sin,\n+  ROUND((ATAN(TAN(total)))::numeric, 2) AS total_tan,\n+  ROUND((RADIANS(DEGREES(total)))::numeric, 2) AS total_deg,\n+  ROUND((POW(total, 2))::numeric, 2) AS total_square,\n+  ROUND((POW(total, 2))::numeric, 2) AS total_square_op\n FROM\n   invoices\n LIMIT\n   5\n\n--- generic\n+++ mssql\n@@ -1,19 +1,24 @@\n SELECT\n   ROUND(total, 2) AS total_original,\n   ABS(ROUND(PI() - total, 2)) AS total_x,\n   FLOOR(total) AS total_floor,\n-  CEIL(total) AS total_ceil,\n+  CEILING(total) AS total_ceil,\n   ROUND(LOG10(total), 3) AS total_log10,\n   ROUND(LOG10(total) / LOG10(2), 3) AS total_log2,\n   ROUND(SQRT(total), 3) AS total_sqrt,\n-  ROUND(EXP(LN(total)), 2) AS total_ln,\n+  ROUND(EXP(LOG(total)), 2) AS total_ln,\n   ROUND(ACOS(COS(total)), 2) AS total_cos,\n   ROUND(ASIN(SIN(total)), 2) AS total_sin,\n   ROUND(ATAN(TAN(total)), 2) AS total_tan,\n   ROUND(RADIANS(DEGREES(total)), 2) AS total_deg,\n-  ROUND(POW(total, 2), 2) AS total_square,\n-  ROUND(POW(total, 2), 2) AS total_square_op\n+  ROUND(POWER(total, 2), 2) AS total_square,\n+  ROUND(POWER(total, 2), 2) AS total_square_op\n FROM\n   invoices\n-LIMIT\n-  5\n+ORDER BY\n+  (\n+    SELECT\n+      NULL\n+  ) OFFSET 0 ROWS\n+FETCH FIRST\n+  5 ROWS ONLY\n\n\n--- generic\n+++ postgres\n@@ -1,19 +1,19 @@\n SELECT\n-  ROUND(total, 2) AS total_original,\n-  ABS(ROUND(PI() - total, 2)) AS total_x,\n+  ROUND((total)::numeric, 2) AS total_original,\n+  ABS(ROUND((PI() - total)::numeric, 2)) AS total_x,\n   FLOOR(total) AS total_floor,\n   CEIL(total) AS total_ceil,\n-  ROUND(LOG10(total), 3) AS total_log10,\n-  ROUND(LOG10(total) / LOG10(2), 3) AS total_log2,\n-  ROUND(SQRT(total), 3) AS total_sqrt,\n-  ROUND(EXP(LN(total)), 2) AS total_ln,\n-  ROUND(ACOS(COS(total)), 2) AS total_cos,\n-  ROUND(ASIN(SIN(total)), 2) AS total_sin,\n-  ROUND(ATAN(TAN(total)), 2) AS total_tan,\n-  ROUND(RADIANS(DEGREES(total)), 2) AS total_deg,\n-  ROUND(POW(total, 2), 2) AS total_square,\n-  ROUND(POW(total, 2), 2) AS total_square_op\n+  ROUND((LOG10(total))::numeric, 3) AS total_log10,\n+  ROUND((LOG10(total) / LOG10(2))::numeric, 3) AS total_log2,\n+  ROUND((SQRT(total))::numeric, 3) AS total_sqrt,\n+  ROUND((EXP(LN(total)))::numeric, 2) AS total_ln,\n+  ROUND((ACOS(COS(total)))::numeric, 2) AS total_cos,\n+  ROUND((ASIN(SIN(total)))::numeric, 2) AS total_sin,\n+  ROUND((ATAN(TAN(total)))::numeric, 2) AS total_tan,\n+  ROUND((RADIANS(DEGREES(total)))::numeric, 2) AS total_deg,\n+  ROUND((POW(total, 2))::numeric, 2) AS total_square,\n+  ROUND((POW(total, 2))::numeric, 2) AS total_square_op\n FROM\n   invoices\n LIMIT\n   5\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__pipelines.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip (Only works on Sqlite implementations which have the extension\\n# installed\\n# https://stackoverflow.com/questions/24037982/how-to-use-regexp-in-sqlite)\\n\\nfrom tracks\\n\\nfilter (name ~= \\\"Love\\\")\\nfilter ((milliseconds / 1000 / 60) | in 3..4)\\nsort track_id\\ntake 1..15\\nselect {name, composer}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/pipelines.prql\nsnapshot_kind: text\n---\n--- generic\n+++ clickhouse\n@@ -1,20 +1,20 @@\n WITH table_0 AS (\n   SELECT\n     name,\n     composer,\n     track_id\n   FROM\n     tracks\n   WHERE\n-    REGEXP(name, 'Love')\n-    AND milliseconds / 1000 / 60 BETWEEN 3 AND 4\n+    match(name, 'Love')\n+    AND ((milliseconds / 1000) / 60) BETWEEN 3 AND 4\n   ORDER BY\n     track_id\n   LIMIT\n     15\n )\n SELECT\n   name,\n   composer\n FROM\n   table_0\n\n--- generic\n+++ duckdb\n@@ -1,20 +1,20 @@\n WITH table_0 AS (\n   SELECT\n     name,\n     composer,\n     track_id\n   FROM\n     tracks\n   WHERE\n-    REGEXP(name, 'Love')\n-    AND milliseconds / 1000 / 60 BETWEEN 3 AND 4\n+    REGEXP_MATCHES(name, 'Love')\n+    AND ((milliseconds / 1000) / 60) BETWEEN 3 AND 4\n   ORDER BY\n     track_id\n   LIMIT\n     15\n )\n SELECT\n   name,\n   composer\n FROM\n   table_0\n\n\n--- generic\n+++ glaredb\n@@ -1,20 +1,20 @@\n WITH table_0 AS (\n   SELECT\n     name,\n     composer,\n     track_id\n   FROM\n     tracks\n   WHERE\n-    REGEXP(name, 'Love')\n-    AND milliseconds / 1000 / 60 BETWEEN 3 AND 4\n+    name ~ 'Love'\n+    AND ((milliseconds * 1.0 / 1000) * 1.0 / 60) BETWEEN 3 AND 4\n   ORDER BY\n     track_id\n   LIMIT\n     15\n )\n SELECT\n   name,\n   composer\n FROM\n   table_0\n\n--- generic\n+++ mysql\n@@ -1,20 +1,20 @@\n WITH table_0 AS (\n   SELECT\n     name,\n     composer,\n     track_id\n   FROM\n     tracks\n   WHERE\n-    REGEXP(name, 'Love')\n-    AND milliseconds / 1000 / 60 BETWEEN 3 AND 4\n+    REGEXP_LIKE(name, 'Love', 'c')\n+    AND ((milliseconds / 1000) / 60) BETWEEN 3 AND 4\n   ORDER BY\n     track_id\n   LIMIT\n     15\n )\n SELECT\n   name,\n   composer\n FROM\n   table_0\n\n--- generic\n+++ postgres\n@@ -1,20 +1,20 @@\n WITH table_0 AS (\n   SELECT\n     name,\n     composer,\n     track_id\n   FROM\n     tracks\n   WHERE\n-    REGEXP(name, 'Love')\n-    AND milliseconds / 1000 / 60 BETWEEN 3 AND 4\n+    name ~ 'Love'\n+    AND ((milliseconds * 1.0 / 1000) * 1.0 / 60) BETWEEN 3 AND 4\n   ORDER BY\n     track_id\n   LIMIT\n     15\n )\n SELECT\n   name,\n   composer\n FROM\n   table_0\n\n--- generic\n+++ redshift\n@@ -1,20 +1,20 @@\n WITH table_0 AS (\n   SELECT\n     name,\n     composer,\n     track_id\n   FROM\n     tracks\n   WHERE\n     REGEXP(name, 'Love')\n-    AND milliseconds / 1000 / 60 BETWEEN 3 AND 4\n+    AND ((milliseconds * 1.0 / 1000) * 1.0 / 60) BETWEEN 3 AND 4\n   ORDER BY\n     track_id\n   LIMIT\n     15\n )\n SELECT\n   name,\n   composer\n FROM\n   table_0\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__read_csv.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip\\n# postgres:skip\\n# mysql:skip\\nfrom (read_csv \\\"data_file_root/media_types.csv\\\")\\nappend (read_json \\\"data_file_root/media_types.json\\\")\\nsort media_type_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/read_csv.prql\n---\n--- generic\n+++ clickhouse\n@@ -1,24 +1,24 @@\n WITH table_0 AS (\n   SELECT\n     *\n   FROM\n-    read_csv('data_file_root/media_types.csv')\n+    file('data_file_root/media_types.csv', 'CSV')\n ),\n table_2 AS (\n   SELECT\n     *\n   FROM\n     table_0\n   UNION\n   ALL\n   SELECT\n     *\n   FROM\n-    read_json('data_file_root/media_types.json')\n+    file('data_file_root/media_types.json', 'Json')\n )\n SELECT\n   *\n FROM\n   table_2\n ORDER BY\n   media_type_id\n\n--- generic\n+++ duckdb\n@@ -1,24 +1,24 @@\n WITH table_0 AS (\n   SELECT\n     *\n   FROM\n-    read_csv('data_file_root/media_types.csv')\n+    read_csv_auto('data_file_root/media_types.csv')\n ),\n table_2 AS (\n   SELECT\n     *\n   FROM\n     table_0\n   UNION\n   ALL\n   SELECT\n     *\n   FROM\n-    read_json('data_file_root/media_types.json')\n+    read_json_auto('data_file_root/media_types.json')\n )\n SELECT\n   *\n FROM\n   table_2\n ORDER BY\n   media_type_id\n\n\n--- generic\n+++ glaredb\n@@ -1,15 +1,15 @@\n WITH table_0 AS (\n   SELECT\n     *\n   FROM\n-    read_csv('data_file_root/media_types.csv')\n+    csv_scan('data_file_root/media_types.csv')\n ),\n table_2 AS (\n   SELECT\n     *\n   FROM\n     table_0\n   UNION\n   ALL\n   SELECT\n     *\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__set_ops_remove.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nlet distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\\n\\nfrom_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2], [2], [3]] }'\\ndistinct\\nremove (from_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2]] }')\\nsort a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/set_ops_remove.prql\nsnapshot_kind: text\n---\n--- generic\n+++ mssql\n@@ -21,21 +21,20 @@\n   ALL\n   SELECT\n     2 AS a\n ),\n table_2 AS (\n   SELECT\n     a\n   FROM\n     table_0\n   EXCEPT\n-    DISTINCT\n   SELECT\n     *\n   FROM\n     table_1\n )\n SELECT\n   a\n FROM\n   table_2\n ORDER BY\n\n\n\n\n--- generic\n+++ sqlite\n@@ -21,21 +21,20 @@\n   ALL\n   SELECT\n     2 AS a\n ),\n table_2 AS (\n   SELECT\n     a\n   FROM\n     table_0\n   EXCEPT\n-    DISTINCT\n   SELECT\n     *\n   FROM\n     table_1\n )\n SELECT\n   a\n FROM\n   table_2\n ORDER BY\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom e=employees\\nfilter first_name != \\\"Mitchell\\\"\\nsort {first_name, last_name}\\n\\n# joining may use HashMerge, which can undo ORDER BY\\njoin manager=employees side:left (e.reports_to == manager.employee_id)\\n\\nselect {e.first_name, e.last_name, manager.first_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort.prql\n---\n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_2.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from albums\\nselect { AA=album_id, artist_id }\\nsort AA\\nfilter AA >= 25\\njoin artists (==artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_2.prql\n---\n--- generic\n+++ clickhouse\n@@ -1,25 +1,25 @@\n WITH table_1 AS (\n   SELECT\n-    album_id AS \"AA\",\n+    album_id AS `AA`,\n     artist_id\n   FROM\n     albums\n ),\n table_0 AS (\n   SELECT\n-    \"AA\",\n+    `AA`,\n     artist_id\n   FROM\n     table_1\n   WHERE\n-    \"AA\" >= 25\n+    `AA` >= 25\n )\n SELECT\n-  table_0.\"AA\",\n+  table_0.`AA`,\n   table_0.artist_id,\n   artists.*\n FROM\n   table_0\n   INNER JOIN artists ON table_0.artist_id = artists.artist_id\n ORDER BY\n-  table_0.\"AA\"\n+  table_0.`AA`\n\n\n\n\n--- generic\n+++ mysql\n@@ -1,25 +1,25 @@\n WITH table_1 AS (\n   SELECT\n-    album_id AS \"AA\",\n+    album_id AS `AA`,\n     artist_id\n   FROM\n     albums\n ),\n table_0 AS (\n   SELECT\n-    \"AA\",\n+    `AA`,\n     artist_id\n   FROM\n     table_1\n   WHERE\n-    \"AA\" >= 25\n+    `AA` >= 25\n )\n SELECT\n-  table_0.\"AA\",\n+  table_0.`AA`,\n   table_0.artist_id,\n   artists.*\n FROM\n   table_0\n   INNER JOIN artists ON table_0.artist_id = artists.artist_id\n ORDER BY\n-  table_0.\"AA\"\n+  table_0.`AA`\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_3.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from [{track_id=0, album_id=1, genre_id=2}]\\nselect { AA=track_id, album_id, genre_id }\\nsort AA\\njoin side:left [{album_id=1, album_title=\\\"Songs\\\"}] (==album_id)\\nselect { AA, AT = album_title ?? \\\"unknown\\\", genre_id }\\nfilter AA < 25\\njoin side:left [{genre_id=1, genre_title=\\\"Rock\\\"}] (==genre_id)\\nselect { AA, AT, GT = genre_title ?? \\\"unknown\\\" }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_3.prql\n---\n--- generic\n+++ clickhouse\n@@ -1,52 +1,52 @@\n WITH table_0 AS (\n   SELECT\n     0 AS track_id,\n     1 AS album_id,\n     2 AS genre_id\n ),\n table_5 AS (\n   SELECT\n-    track_id AS \"AA\",\n+    track_id AS `AA`,\n     genre_id,\n     album_id\n   FROM\n     table_0\n ),\n table_1 AS (\n   SELECT\n     1 AS album_id,\n     'Songs' AS album_title\n ),\n table_4 AS (\n   SELECT\n-    table_5.\"AA\",\n-    COALESCE(table_1.album_title, 'unknown') AS \"AT\",\n+    table_5.`AA`,\n+    COALESCE(table_1.album_title, 'unknown') AS `AT`,\n     table_5.genre_id\n   FROM\n     table_5\n     LEFT OUTER JOIN table_1 ON table_5.album_id = table_1.album_id\n ),\n table_3 AS (\n   SELECT\n-    \"AA\",\n-    \"AT\",\n+    `AA`,\n+    `AT`,\n     genre_id\n   FROM\n     table_4\n   WHERE\n-    \"AA\" < 25\n+    `AA` < 25\n ),\n table_2 AS (\n   SELECT\n     1 AS genre_id,\n     'Rock' AS genre_title\n )\n SELECT\n-  table_3.\"AA\",\n-  table_3.\"AT\",\n-  COALESCE(table_2.genre_title, 'unknown') AS \"GT\"\n+  table_3.`AA`,\n+  table_3.`AT`,\n+  COALESCE(table_2.genre_title, 'unknown') AS `GT`\n FROM\n   table_3\n   LEFT OUTER JOIN table_2 ON table_3.genre_id = table_2.genre_id\n ORDER BY\n-  table_3.\"AA\"\n+  table_3.`AA`\n\n\n\n\n--- generic\n+++ mysql\n@@ -1,52 +1,52 @@\n WITH table_0 AS (\n   SELECT\n     0 AS track_id,\n     1 AS album_id,\n     2 AS genre_id\n ),\n table_5 AS (\n   SELECT\n-    track_id AS \"AA\",\n+    track_id AS `AA`,\n     genre_id,\n     album_id\n   FROM\n     table_0\n ),\n table_1 AS (\n   SELECT\n     1 AS album_id,\n     'Songs' AS album_title\n ),\n table_4 AS (\n   SELECT\n-    table_5.\"AA\",\n-    COALESCE(table_1.album_title, 'unknown') AS \"AT\",\n+    table_5.`AA`,\n+    COALESCE(table_1.album_title, 'unknown') AS `AT`,\n     table_5.genre_id\n   FROM\n     table_5\n     LEFT OUTER JOIN table_1 ON table_5.album_id = table_1.album_id\n ),\n table_3 AS (\n   SELECT\n-    \"AA\",\n-    \"AT\",\n+    `AA`,\n+    `AT`,\n     genre_id\n   FROM\n     table_4\n   WHERE\n-    \"AA\" < 25\n+    `AA` < 25\n ),\n table_2 AS (\n   SELECT\n     1 AS genre_id,\n     'Rock' AS genre_title\n )\n SELECT\n-  table_3.\"AA\",\n-  table_3.\"AT\",\n-  COALESCE(table_2.genre_title, 'unknown') AS \"GT\"\n+  table_3.`AA`,\n+  table_3.`AT`,\n+  COALESCE(table_2.genre_title, 'unknown') AS `GT`\n FROM\n   table_3\n   LEFT OUTER JOIN table_2 ON table_3.genre_id = table_2.genre_id\n ORDER BY\n-  table_3.\"AA\"\n+  table_3.`AA`\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__switch.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# glaredb:skip (May be a bag of String type conversion for Postgres Client)\\n# mssql:test\\nfrom tracks\\nsort milliseconds\\nselect display = case [\\n    composer != null => composer,\\n    genre_id < 17 => 'no composer',\\n    true => f'unknown composer'\\n]\\ntake 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/switch.prql\n---\n--- generic\n+++ mssql\n@@ -2,20 +2,20 @@\n   SELECT\n     CASE\n       WHEN composer IS NOT NULL THEN composer\n       WHEN genre_id < 17 THEN 'no composer'\n       ELSE 'unknown composer'\n     END AS display,\n     milliseconds\n   FROM\n     tracks\n   ORDER BY\n-    milliseconds\n-  LIMIT\n-    10\n+    milliseconds OFFSET 0 ROWS\n+  FETCH FIRST\n+    10 ROWS ONLY\n )\n SELECT\n   display\n FROM\n   table_0\n ORDER BY\n   milliseconds\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {+track_id}\\ntake 3..5\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/take.prql\n---\n--- generic\n+++ mssql\n@@ -1,8 +1,8 @@\n SELECT\n   *\n FROM\n   tracks\n ORDER BY\n-  track_id\n-LIMIT\n-  3 OFFSET 2\n+  track_id OFFSET 2 ROWS\n+FETCH FIRST\n+  3 ROWS ONLY\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__text_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# glaredb:skip — TODO: started raising an error on 2024-05-20; see `window.prql`\\n# for more details\\nfrom albums\\nselect {\\n    title,\\n    title_and_spaces = f\\\"  {title}  \\\",\\n    low = (title | text.lower),\\n    up = (title | text.upper),\\n    ltrimmed = (title | text.ltrim),\\n    rtrimmed = (title | text.rtrim),\\n    trimmed = (title | text.trim),\\n    len = (title | text.length),\\n    subs = (title | text.extract 2 5),\\n    replace = (title | text.replace \\\"al\\\" \\\"PIKA\\\"),\\n}\\nsort {title}\\nfilter (title | text.starts_with \\\"Black\\\") || (title | text.contains \\\"Sabbath\\\") || (title | text.ends_with \\\"os\\\")\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/text_module.prql\n---\n--- generic\n+++ clickhouse\n@@ -2,33 +2,33 @@\n   SELECT\n     title,\n     CONCAT('  ', title, '  ') AS title_and_spaces,\n     LOWER(title) AS low,\n     UPPER(title) AS up,\n     LTRIM(title) AS ltrimmed,\n     RTRIM(title) AS rtrimmed,\n     TRIM(title) AS trimmed,\n     CHAR_LENGTH(title) AS len,\n     SUBSTRING(title, 2, 5) AS subs,\n-    REPLACE(title, 'al', 'PIKA') AS \"replace\"\n+    REPLACE(title, 'al', 'PIKA') AS `replace`\n   FROM\n     albums\n )\n SELECT\n   title,\n   title_and_spaces,\n   low,\n   up,\n   ltrimmed,\n   rtrimmed,\n   trimmed,\n   len,\n   subs,\n-  \"replace\"\n+  `replace`\n FROM\n   table_0\n WHERE\n   title LIKE CONCAT('Black', '%')\n   OR title LIKE CONCAT('%', 'Sabbath', '%')\n   OR title LIKE CONCAT('%', 'os')\n ORDER BY\n   title\n\n--- generic\n+++ duckdb\n@@ -1,20 +1,20 @@\n WITH table_0 AS (\n   SELECT\n     title,\n     CONCAT('  ', title, '  ') AS title_and_spaces,\n     LOWER(title) AS low,\n     UPPER(title) AS up,\n     LTRIM(title) AS ltrimmed,\n     RTRIM(title) AS rtrimmed,\n     TRIM(title) AS trimmed,\n-    CHAR_LENGTH(title) AS len,\n+    LENGTH(title) AS len,\n     SUBSTRING(title, 2, 5) AS subs,\n     REPLACE(title, 'al', 'PIKA') AS \"replace\"\n   FROM\n     albums\n )\n SELECT\n   title,\n   title_and_spaces,\n   low,\n   up,\n\n\n--- generic\n+++ mssql\n@@ -1,20 +1,20 @@\n WITH table_0 AS (\n   SELECT\n     title,\n     CONCAT('  ', title, '  ') AS title_and_spaces,\n     LOWER(title) AS low,\n     UPPER(title) AS up,\n     LTRIM(title) AS ltrimmed,\n     RTRIM(title) AS rtrimmed,\n     TRIM(title) AS trimmed,\n-    CHAR_LENGTH(title) AS len,\n+    LEN(title) AS len,\n     SUBSTRING(title, 2, 5) AS subs,\n     REPLACE(title, 'al', 'PIKA') AS \"replace\"\n   FROM\n     albums\n )\n SELECT\n   title,\n   title_and_spaces,\n   low,\n   up,\n\n--- generic\n+++ mysql\n@@ -2,33 +2,33 @@\n   SELECT\n     title,\n     CONCAT('  ', title, '  ') AS title_and_spaces,\n     LOWER(title) AS low,\n     UPPER(title) AS up,\n     LTRIM(title) AS ltrimmed,\n     RTRIM(title) AS rtrimmed,\n     TRIM(title) AS trimmed,\n     CHAR_LENGTH(title) AS len,\n     SUBSTRING(title, 2, 5) AS subs,\n-    REPLACE(title, 'al', 'PIKA') AS \"replace\"\n+    REPLACE(title, 'al', 'PIKA') AS `replace`\n   FROM\n     albums\n )\n SELECT\n   title,\n   title_and_spaces,\n   low,\n   up,\n   ltrimmed,\n   rtrimmed,\n   trimmed,\n   len,\n   subs,\n-  \"replace\"\n+  `replace`\n FROM\n   table_0\n WHERE\n   title LIKE CONCAT('Black', '%')\n   OR title LIKE CONCAT('%', 'Sabbath', '%')\n   OR title LIKE CONCAT('%', 'os')\n ORDER BY\n   title\n\n--- generic\n+++ postgres\n@@ -1,21 +1,21 @@\n WITH table_0 AS (\n   SELECT\n     title,\n     CONCAT('  ', title, '  ') AS title_and_spaces,\n     LOWER(title) AS low,\n     UPPER(title) AS up,\n     LTRIM(title) AS ltrimmed,\n     RTRIM(title) AS rtrimmed,\n     TRIM(title) AS trimmed,\n     CHAR_LENGTH(title) AS len,\n-    SUBSTRING(title, 2, 5) AS subs,\n+    SUBSTR(title, 2, 5) AS subs,\n     REPLACE(title, 'al', 'PIKA') AS \"replace\"\n   FROM\n     albums\n )\n SELECT\n   title,\n   title_and_spaces,\n   low,\n   up,\n   ltrimmed,\n\n--- generic\n+++ redshift\n@@ -1,14 +1,14 @@\n WITH table_0 AS (\n   SELECT\n     title,\n-    CONCAT('  ', title, '  ') AS title_and_spaces,\n+    '  ' || title || '  ' AS title_and_spaces,\n     LOWER(title) AS low,\n     UPPER(title) AS up,\n     LTRIM(title) AS ltrimmed,\n     RTRIM(title) AS rtrimmed,\n     TRIM(title) AS trimmed,\n     CHAR_LENGTH(title) AS len,\n     SUBSTRING(title, 2, 5) AS subs,\n     REPLACE(title, 'al', 'PIKA') AS \"replace\"\n   FROM\n     albums\n@@ -21,14 +21,14 @@\n   ltrimmed,\n   rtrimmed,\n   trimmed,\n   len,\n   subs,\n   \"replace\"\n FROM\n   table_0\n WHERE\n   title LIKE CONCAT('Black', '%')\n-  OR title LIKE CONCAT('%', 'Sabbath', '%')\n+  OR title LIKE '%' || 'Sabbath' || '%'\n   OR title LIKE CONCAT('%', 'os')\n ORDER BY\n   title\n\n--- generic\n+++ sqlite\n@@ -1,34 +1,34 @@\n WITH table_0 AS (\n   SELECT\n     title,\n-    CONCAT('  ', title, '  ') AS title_and_spaces,\n+    '  ' || title || '  ' AS title_and_spaces,\n     LOWER(title) AS low,\n     UPPER(title) AS up,\n     LTRIM(title) AS ltrimmed,\n     RTRIM(title) AS rtrimmed,\n     TRIM(title) AS trimmed,\n-    CHAR_LENGTH(title) AS len,\n+    LENGTH(title) AS len,\n     SUBSTRING(title, 2, 5) AS subs,\n     REPLACE(title, 'al', 'PIKA') AS \"replace\"\n   FROM\n     albums\n )\n SELECT\n   title,\n   title_and_spaces,\n   low,\n   up,\n   ltrimmed,\n   rtrimmed,\n   trimmed,\n   len,\n   subs,\n   \"replace\"\n FROM\n   table_0\n WHERE\n-  title LIKE CONCAT('Black', '%')\n-  OR title LIKE CONCAT('%', 'Sabbath', '%')\n-  OR title LIKE CONCAT('%', 'os')\n+  title LIKE 'Black' || '%'\n+  OR title LIKE '%' || 'Sabbath' || '%'\n+  OR title LIKE '%' || 'os'\n ORDER BY\n   title\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__window.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip problems with DISTINCT ON\\n# glaredb:skip — TODO: started raising an error on 2024-05-20, from https://github.com/PRQL/prql/actions/runs/9154902656/job/25198160283:\\n    # ERROR: This feature is not implemented: Unsupported ast node in sqltorel:\\n    # Substring { expr: Identifier(Ident { value: \\\"title\\\", quote_style: None }),\\n    # substring_from: Some(Value(Number(\\\"2\\\", false))), substring_for:\\n    # Some(Value(Number(\\\"5\\\", false))), special: true }\\nfrom tracks\\ngroup genre_id (\\n  sort milliseconds\\n  derive {\\n    num = row_number this,\\n    total = count this,\\n    last_val = last track_id,\\n  }\\n  take 10\\n)\\nsort {genre_id, milliseconds}\\nselect {track_id, genre_id, num, total, last_val}\\nfilter genre_id >= 22\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/window.prql\n---\n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__aggregation.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mysql:skip\\n# clickhouse:skip\\n# glaredb:skip (the string_agg function is not supported)\\nfrom tracks\\nfilter genre_id == 100\\nderive empty_name = name == ''\\naggregate {sum track_id, concat_array name, all empty_name, any empty_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/aggregation.prql\n---\nframes:\n- - 1:101-123\n  - columns:\n    - !All\n      input_id: 122\n      except: []\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:124-154\n  - columns:\n    - !All\n      input_id: 122\n      except: []\n    - !Single\n      name:\n      - empty_name\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:155-230\n  - columns:\n    - !Single\n      name: null\n      target_id: 135\n      target_name: null\n    - !Single\n      name: null\n      target_id: 138\n      target_name: null\n    - !Single\n      name: null\n      target_id: 141\n      target_name: null\n    - !Single\n      name: null\n      target_id: 144\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 122\n  kind: Ident\n  span: 1:89-100\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 128\n- id: 124\n  kind: RqOperator\n  span: 1:108-123\n  targets:\n  - 126\n  - 127\n  parent: 128\n- id: 126\n  kind: Ident\n  span: 1:108-116\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 122\n- id: 127\n  kind: Literal\n  span: 1:120-123\n- id: 128\n  kind: 'TransformCall: Filter'\n  span: 1:101-123\n  children:\n  - 122\n  - 124\n  parent: 134\n- id: 129\n  kind: RqOperator\n  span: 1:144-154\n  alias: empty_name\n  targets:\n  - 131\n  - 132\n  parent: 133\n- id: 131\n  kind: Ident\n  span: 1:144-148\n  ident: !Ident\n  - this\n  - tracks\n  - name\n  targets:\n  - 122\n- id: 132\n  kind: Literal\n  span: 1:152-154\n- id: 133\n  kind: Tuple\n  span: 1:144-154\n  children:\n  - 129\n  parent: 134\n- id: 134\n  kind: 'TransformCall: Derive'\n  span: 1:124-154\n  children:\n  - 128\n  - 133\n  parent: 148\n- id: 135\n  kind: RqOperator\n  span: 1:166-178\n  targets:\n  - 137\n  parent: 147\n- id: 137\n  kind: Ident\n  span: 1:170-178\n  ident: !Ident\n  - this\n  - tracks\n  - track_id\n  targets:\n  - 122\n- id: 138\n  kind: RqOperator\n  span: 1:180-197\n  targets:\n  - 140\n  parent: 147\n- id: 140\n  kind: Ident\n  span: 1:193-197\n  ident: !Ident\n  - this\n  - tracks\n  - name\n  targets:\n  - 122\n- id: 141\n  kind: RqOperator\n  span: 1:199-213\n  targets:\n  - 143\n  parent: 147\n- id: 143\n  kind: Ident\n  span: 1:203-213\n  ident: !Ident\n  - this\n  - empty_name\n  targets:\n  - 129\n- id: 144\n  kind: RqOperator\n  span: 1:215-229\n  targets:\n  - 146\n  parent: 147\n- id: 146\n  kind: Ident\n  span: 1:219-229\n  ident: !Ident\n  - this\n  - empty_name\n  targets:\n  - 129\n- id: 147\n  kind: Tuple\n  span: 1:165-230\n  children:\n  - 135\n  - 138\n  - 141\n  - 144\n  parent: 148\n- id: 148\n  kind: 'TransformCall: Aggregate'\n  span: 1:155-230\n  children:\n  - 134\n  - 147\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:89-93\n              args:\n              - Ident:\n                - tracks\n                span: 1:94-100\n            span: 1:89-100\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:101-107\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - genre_id\n                    span: 1:108-116\n                  op: Eq\n                  right:\n                    Literal:\n                      Integer: 100\n                    span: 1:120-123\n                span: 1:108-123\n            span: 1:101-123\n          - FuncCall:\n              name:\n                Ident:\n                - derive\n                span: 1:124-130\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - name\n                    span: 1:144-148\n                  op: Eq\n                  right:\n                    Literal:\n                      String: ''\n                    span: 1:152-154\n                span: 1:144-154\n                alias: empty_name\n            span: 1:124-154\n          - FuncCall:\n              name:\n                Ident:\n                - aggregate\n                span: 1:155-164\n              args:\n              - Tuple:\n                - FuncCall:\n                    name:\n                      Ident:\n                      - sum\n                      span: 1:166-169\n                    args:\n                    - Ident:\n                      - track_id\n                      span: 1:170-178\n                  span: 1:166-178\n                - FuncCall:\n                    name:\n                      Ident:\n                      - concat_array\n                      span: 1:180-192\n                    args:\n                    - Ident:\n                      - name\n                      span: 1:193-197\n                  span: 1:180-197\n                - FuncCall:\n                    name:\n                      Ident:\n                      - all\n                      span: 1:199-202\n                    args:\n                    - Ident:\n                      - empty_name\n                      span: 1:203-213\n                  span: 1:199-213\n                - FuncCall:\n                    name:\n                      Ident:\n                      - any\n                      span: 1:215-218\n                    args:\n                    - Ident:\n                      - empty_name\n                      span: 1:219-229\n                  span: 1:215-229\n                span: 1:165-230\n            span: 1:155-230\n        span: 1:89-230\n    span: 1:0-230\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__append_select.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 10..15\\nappend (\\n  from invoices\\n  select { customer_id, invoice_id, billing_country }\\n  take 40..45\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select.prql\n---\nframes:\n- - 1:14-65\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 146\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 147\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 148\n      target_name: null\n    inputs:\n    - id: 144\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:66-77\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 146\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 147\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 148\n      target_name: null\n    inputs:\n    - id: 144\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:105-156\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 127\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 128\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 125\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:159-170\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 127\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 128\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 125\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:78-172\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 146\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 147\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 148\n      target_name: null\n    inputs:\n    - id: 144\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 125\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:173-211\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 156\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 157\n      target_name: null\n    inputs:\n    - id: 144\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 125\n      name: invoices\n      table:\n      - default_db\n      - invoices\nnodes:\n- id: 125\n  kind: Ident\n  span: 1:89-102\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 131\n- id: 127\n  kind: Ident\n  span: 1:114-125\n  ident: !Ident\n  - this\n  - invoices\n  - customer_id\n  targets:\n  - 125\n  parent: 130\n- id: 128\n  kind: Ident\n  span: 1:127-137\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 125\n  parent: 130\n- id: 129\n  kind: Ident\n  span: 1:139-154\n  ident: !Ident\n  - this\n  - invoices\n  - billing_country\n  targets:\n  - 125\n  parent: 130\n- id: 130\n  kind: Tuple\n  span: 1:112-156\n  children:\n  - 127\n  - 128\n  - 129\n  parent: 131\n- id: 131\n  kind: 'TransformCall: Select'\n  span: 1:105-156\n  children:\n  - 125\n  - 130\n  parent: 135\n- id: 132\n  kind: Literal\n  span: 1:164-166\n  alias: start\n  parent: 135\n- id: 133\n  kind: Literal\n  span: 1:168-170\n  alias: end\n  parent: 135\n- id: 135\n  kind: 'TransformCall: Take'\n  span: 1:159-170\n  children:\n  - 131\n  - 132\n  - 133\n  parent: 155\n- id: 144\n  kind: Ident\n  span: 1:0-13\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 150\n- id: 146\n  kind: Ident\n  span: 1:23-34\n  ident: !Ident\n  - this\n  - invoices\n  - customer_id\n  targets:\n  - 144\n  parent: 149\n- id: 147\n  kind: Ident\n  span: 1:36-46\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 144\n  parent: 149\n- id: 148\n  kind: Ident\n  span: 1:48-63\n  ident: !Ident\n  - this\n  - invoices\n  - billing_country\n  targets:\n  - 144\n  parent: 149\n- id: 149\n  kind: Tuple\n  span: 1:21-65\n  children:\n  - 146\n  - 147\n  - 148\n  parent: 150\n- id: 150\n  kind: 'TransformCall: Select'\n  span: 1:14-65\n  children:\n  - 144\n  - 149\n  parent: 154\n- id: 151\n  kind: Literal\n  span: 1:71-73\n  alias: start\n  parent: 154\n- id: 152\n  kind: Literal\n  span: 1:75-77\n  alias: end\n  parent: 154\n- id: 154\n  kind: 'TransformCall: Take'\n  span: 1:66-77\n  children:\n  - 150\n  - 151\n  - 152\n  parent: 155\n- id: 155\n  kind: 'TransformCall: Append'\n  span: 1:78-172\n  children:\n  - 154\n  - 135\n  parent: 159\n- id: 156\n  kind: Ident\n  span: 1:182-197\n  ident: !Ident\n  - this\n  - invoices\n  - billing_country\n  targets:\n  - 148\n  parent: 158\n- id: 157\n  kind: Ident\n  span: 1:199-209\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 147\n  parent: 158\n- id: 158\n  kind: Tuple\n  span: 1:180-211\n  children:\n  - 156\n  - 157\n  parent: 159\n- id: 159\n  kind: 'TransformCall: Select'\n  span: 1:173-211\n  children:\n  - 155\n  - 158\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:0-4\n              args:\n              - Ident:\n                - invoices\n                span: 1:5-13\n            span: 1:0-13\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:14-20\n              args:\n              - Tuple:\n                - Ident:\n                  - customer_id\n                  span: 1:23-34\n                - Ident:\n                  - invoice_id\n                  span: 1:36-46\n                - Ident:\n                  - billing_country\n                  span: 1:48-63\n                span: 1:21-65\n            span: 1:14-65\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:66-70\n              args:\n              - Range:\n                  start:\n                    Literal:\n                      Integer: 10\n                    span: 1:71-73\n                  end:\n                    Literal:\n                      Integer: 15\n                    span: 1:75-77\n                span: 1:71-77\n            span: 1:66-77\n          - FuncCall:\n              name:\n                Ident:\n                - append\n                span: 1:78-84\n              args:\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - from\n                        span: 1:89-93\n                      args:\n                      - Ident:\n                        - invoices\n                        span: 1:94-102\n                    span: 1:89-102\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - select\n                        span: 1:105-111\n                      args:\n                      - Tuple:\n                        - Ident:\n                          - customer_id\n                          span: 1:114-125\n                        - Ident:\n                          - invoice_id\n                          span: 1:127-137\n                        - Ident:\n                          - billing_country\n                          span: 1:139-154\n                        span: 1:112-156\n                    span: 1:105-156\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - take\n                        span: 1:159-163\n                      args:\n                      - Range:\n                          start:\n                            Literal:\n                              Integer: 40\n                            span: 1:164-166\n                          end:\n                            Literal:\n                              Integer: 45\n                            span: 1:168-170\n                        span: 1:164-170\n                    span: 1:159-170\n                span: 1:89-170\n            span: 1:78-172\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:173-179\n              args:\n              - Tuple:\n                - Ident:\n                  - billing_country\n                  span: 1:182-197\n                - Ident:\n                  - invoice_id\n                  span: 1:199-209\n                span: 1:180-211\n            span: 1:173-211\n        span: 1:0-211\n    span: 1:0-211\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__append_select_compute.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nderive total = case [total < 10 => total * 2, true => total]\\nselect { customer_id, invoice_id, total }\\ntake 5\\nappend (\\n  from invoice_items\\n  derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\\n  select { invoice_line_id, invoice_id, unit_price }\\n  take 5\\n)\\nselect { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_compute.prql\n---\nframes:\n- - 1:14-74\n  - columns:\n    - !All\n      input_id: 162\n      except: []\n    - !Single\n      name:\n      - total\n      target_id: 164\n      target_name: null\n    inputs:\n    - id: 162\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:75-116\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 177\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 178\n      target_name: null\n    - !Single\n      name:\n      - total\n      target_id: 179\n      target_name: null\n    inputs:\n    - id: 162\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:117-123\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 177\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 178\n      target_name: null\n    - !Single\n      name:\n      - total\n      target_id: 179\n      target_name: null\n    inputs:\n    - id: 162\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:156-235\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    - !Single\n      name:\n      - unit_price\n      target_id: 130\n      target_name: null\n    inputs:\n    - id: 128\n      name: invoice_items\n      table:\n      - default_db\n      - invoice_items\n- - 1:238-288\n  - columns:\n    - !Single\n      name:\n      - invoice_items\n      - invoice_line_id\n      target_id: 143\n      target_name: null\n    - !Single\n      name:\n      - invoice_items\n      - invoice_id\n      target_id: 144\n      target_name: null\n    - !Single\n      name:\n      - unit_price\n      target_id: 145\n      target_name: null\n    inputs:\n    - id: 128\n      name: invoice_items\n      table:\n      - default_db\n      - invoice_items\n- - 1:291-297\n  - columns:\n    - !Single\n      name:\n      - invoice_items\n      - invoice_line_id\n      target_id: 143\n      target_name: null\n    - !Single\n      name:\n      - invoice_items\n      - invoice_id\n      target_id: 144\n      target_name: null\n    - !Single\n      name:\n      - unit_price\n      target_id: 145\n      target_name: null\n    inputs:\n    - id: 128\n      name: invoice_items\n      table:\n      - default_db\n      - invoice_items\n- - 1:124-299\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 177\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 178\n      target_name: null\n    - !Single\n      name:\n      - total\n      target_id: 179\n      target_name: null\n    inputs:\n    - id: 162\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 128\n      name: invoice_items\n      table:\n      - default_db\n      - invoice_items\n- - 1:300-369\n  - columns:\n    - !Single\n      name:\n      - a\n      target_id: 186\n      target_name: null\n    - !Single\n      name:\n      - b\n      target_id: 190\n      target_name: null\n    inputs:\n    - id: 162\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 128\n      name: invoice_items\n      table:\n      - default_db\n      - invoice_items\nnodes:\n- id: 128\n  kind: Ident\n  span: 1:135-153\n  ident: !Ident\n  - default_db\n  - invoice_items\n  parent: 142\n- id: 130\n  kind: Case\n  span: 1:176-235\n  alias: unit_price\n  targets:\n  - 131\n  - 135\n  - 139\n  - 140\n  parent: 141\n- id: 131\n  kind: RqOperator\n  span: 1:182-196\n  targets:\n  - 133\n  - 134\n- id: 133\n  kind: Ident\n  span: 1:182-192\n  ident: !Ident\n  - this\n  - invoice_items\n  - unit_price\n  targets:\n  - 128\n- id: 134\n  kind: Literal\n  span: 1:195-196\n- id: 135\n  kind: RqOperator\n  span: 1:200-214\n  targets:\n  - 137\n  - 138\n- id: 137\n  kind: Ident\n  span: 1:200-210\n  ident: !Ident\n  - this\n  - invoice_items\n  - unit_price\n  targets:\n  - 128\n- id: 138\n  kind: Literal\n  span: 1:213-214\n- id: 139\n  kind: Literal\n  span: 1:216-220\n- id: 140\n  kind: Ident\n  span: 1:224-234\n  ident: !Ident\n  - this\n  - invoice_items\n  - unit_price\n  targets:\n  - 128\n- id: 141\n  kind: Tuple\n  span: 1:176-235\n  children:\n  - 130\n  parent: 142\n- id: 142\n  kind: 'TransformCall: Derive'\n  span: 1:156-235\n  children:\n  - 128\n  - 141\n  parent: 147\n- id: 143\n  kind: Ident\n  span: 1:247-262\n  ident: !Ident\n  - this\n  - invoice_items\n  - invoice_line_id\n  targets:\n  - 128\n  parent: 146\n- id: 144\n  kind: Ident\n  span: 1:264-274\n  ident: !Ident\n  - this\n  - invoice_items\n  - invoice_id\n  targets:\n  - 128\n  parent: 146\n- id: 145\n  kind: Ident\n  span: 1:276-286\n  ident: !Ident\n  - this\n  - unit_price\n  targets:\n  - 130\n  parent: 146\n- id: 146\n  kind: Tuple\n  span: 1:245-288\n  children:\n  - 143\n  - 144\n  - 145\n  parent: 147\n- id: 147\n  kind: 'TransformCall: Select'\n  span: 1:238-288\n  children:\n  - 142\n  - 146\n  parent: 149\n- id: 149\n  kind: 'TransformCall: Take'\n  span: 1:291-297\n  children:\n  - 147\n  - 150\n  parent: 185\n- id: 150\n  kind: Literal\n  parent: 149\n- id: 162\n  kind: Ident\n  span: 1:0-13\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 176\n- id: 164\n  kind: Case\n  span: 1:29-74\n  alias: total\n  targets:\n  - 165\n  - 169\n  - 173\n  - 174\n  parent: 175\n- id: 165\n  kind: RqOperator\n  span: 1:35-45\n  targets:\n  - 167\n  - 168\n- id: 167\n  kind: Ident\n  span: 1:35-40\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 162\n- id: 168\n  kind: Literal\n  span: 1:43-45\n- id: 169\n  kind: RqOperator\n  span: 1:49-58\n  targets:\n  - 171\n  - 172\n- id: 171\n  kind: Ident\n  span: 1:49-54\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 162\n- id: 172\n  kind: Literal\n  span: 1:57-58\n- id: 173\n  kind: Literal\n  span: 1:60-64\n- id: 174\n  kind: Ident\n  span: 1:68-73\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 162\n- id: 175\n  kind: Tuple\n  span: 1:29-74\n  children:\n  - 164\n  parent: 176\n- id: 176\n  kind: 'TransformCall: Derive'\n  span: 1:14-74\n  children:\n  - 162\n  - 175\n  parent: 181\n- id: 177\n  kind: Ident\n  span: 1:84-95\n  ident: !Ident\n  - this\n  - invoices\n  - customer_id\n  targets:\n  - 162\n  parent: 180\n- id: 178\n  kind: Ident\n  span: 1:97-107\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 162\n  parent: 180\n- id: 179\n  kind: Ident\n  span: 1:109-114\n  ident: !Ident\n  - this\n  - total\n  targets:\n  - 164\n  parent: 180\n- id: 180\n  kind: Tuple\n  span: 1:82-116\n  children:\n  - 177\n  - 178\n  - 179\n  parent: 181\n- id: 181\n  kind: 'TransformCall: Select'\n  span: 1:75-116\n  children:\n  - 176\n  - 180\n  parent: 183\n- id: 183\n  kind: 'TransformCall: Take'\n  span: 1:117-123\n  children:\n  - 181\n  - 184\n  parent: 185\n- id: 184\n  kind: Literal\n  parent: 183\n- id: 185\n  kind: 'TransformCall: Append'\n  span: 1:124-299\n  children:\n  - 183\n  - 149\n  parent: 198\n- id: 186\n  kind: RqOperator\n  span: 1:313-328\n  alias: a\n  targets:\n  - 188\n  - 189\n  parent: 197\n- id: 188\n  kind: Ident\n  span: 1:313-324\n  ident: !Ident\n  - this\n  - invoices\n  - customer_id\n  targets:\n  - 177\n- id: 189\n  kind: Literal\n  span: 1:327-328\n- id: 190\n  kind: RqOperator\n  span: 1:334-367\n  alias: b\n  targets:\n  - 192\n  - 193\n  parent: 197\n- id: 192\n  kind: Literal\n  span: 1:345-346\n- id: 193\n  kind: RqOperator\n  span: 1:348-366\n  targets:\n  - 195\n  - 196\n- id: 195\n  kind: Ident\n  span: 1:348-358\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 178\n- id: 196\n  kind: Ident\n  span: 1:361-366\n  ident: !Ident\n  - this\n  - total\n  targets:\n  - 179\n- id: 197\n  kind: Tuple\n  span: 1:307-369\n  children:\n  - 186\n  - 190\n  parent: 198\n- id: 198\n  kind: 'TransformCall: Select'\n  span: 1:300-369\n  children:\n  - 185\n  - 197\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:0-4\n              args:\n              - Ident:\n                - invoices\n                span: 1:5-13\n            span: 1:0-13\n          - FuncCall:\n              name:\n                Ident:\n                - derive\n                span: 1:14-20\n              args:\n              - Case:\n                - condition:\n                    Binary:\n                      left:\n                        Ident:\n                        - total\n                        span: 1:35-40\n                      op: Lt\n                      right:\n                        Literal:\n                          Integer: 10\n                        span: 1:43-45\n                    span: 1:35-45\n                  value:\n                    Binary:\n                      left:\n                        Ident:\n                        - total\n                        span: 1:49-54\n                      op: Mul\n                      right:\n                        Literal:\n                          Integer: 2\n                        span: 1:57-58\n                    span: 1:49-58\n                - condition:\n                    Literal:\n                      Boolean: true\n                    span: 1:60-64\n                  value:\n                    Ident:\n                    - total\n                    span: 1:68-73\n                span: 1:29-74\n                alias: total\n            span: 1:14-74\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:75-81\n              args:\n              - Tuple:\n                - Ident:\n                  - customer_id\n                  span: 1:84-95\n                - Ident:\n                  - invoice_id\n                  span: 1:97-107\n                - Ident:\n                  - total\n                  span: 1:109-114\n                span: 1:82-116\n            span: 1:75-116\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:117-121\n              args:\n              - Literal:\n                  Integer: 5\n                span: 1:122-123\n            span: 1:117-123\n          - FuncCall:\n              name:\n                Ident:\n                - append\n                span: 1:124-130\n              args:\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - from\n                        span: 1:135-139\n                      args:\n                      - Ident:\n                        - invoice_items\n                        span: 1:140-153\n                    span: 1:135-153\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - derive\n                        span: 1:156-162\n                      args:\n                      - Case:\n                        - condition:\n                            Binary:\n                              left:\n                                Ident:\n                                - unit_price\n                                span: 1:182-192\n                              op: Lt\n                              right:\n                                Literal:\n                                  Integer: 1\n                                span: 1:195-196\n                            span: 1:182-196\n                          value:\n                            Binary:\n                              left:\n                                Ident:\n                                - unit_price\n                                span: 1:200-210\n                              op: Mul\n                              right:\n                                Literal:\n                                  Integer: 2\n                                span: 1:213-214\n                            span: 1:200-214\n                        - condition:\n                            Literal:\n                              Boolean: true\n                            span: 1:216-220\n                          value:\n                            Ident:\n                            - unit_price\n                            span: 1:224-234\n                        span: 1:176-235\n                        alias: unit_price\n                    span: 1:156-235\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - select\n                        span: 1:238-244\n                      args:\n                      - Tuple:\n                        - Ident:\n                          - invoice_line_id\n                          span: 1:247-262\n                        - Ident:\n                          - invoice_id\n                          span: 1:264-274\n                        - Ident:\n                          - unit_price\n                          span: 1:276-286\n                        span: 1:245-288\n                    span: 1:238-288\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - take\n                        span: 1:291-295\n                      args:\n                      - Literal:\n                          Integer: 5\n                        span: 1:296-297\n                    span: 1:291-297\n                span: 1:135-297\n            span: 1:124-299\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:300-306\n              args:\n              - Tuple:\n                - Binary:\n                    left:\n                      Ident:\n                      - customer_id\n                      span: 1:313-324\n                    op: Mul\n                    right:\n                      Literal:\n                        Integer: 2\n                      span: 1:327-328\n                  span: 1:313-328\n                  alias: a\n                - FuncCall:\n                    name:\n                      Ident:\n                      - math\n                      - round\n                      span: 1:334-344\n                    args:\n                    - Literal:\n                        Integer: 1\n                      span: 1:345-346\n                    - Binary:\n                        left:\n                          Ident:\n                          - invoice_id\n                          span: 1:348-358\n                        op: Mul\n                        right:\n                          Ident:\n                          - total\n                          span: 1:361-366\n                      span: 1:348-366\n                  span: 1:334-367\n                  alias: b\n                span: 1:307-369\n            span: 1:300-369\n        span: 1:0-369\n    span: 1:0-369\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__append_select_multiple_with_null.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 5\\nappend (\\n  from employees\\n  select { employee_id, employee_id, country }\\n  take 5\\n)\\nappend (\\n  from invoice_items\\n  select { invoice_line_id, invoice_id, null }\\n  take 5\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql\n---\nframes:\n- - 1:14-65\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 166\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 167\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 168\n      target_name: null\n    inputs:\n    - id: 164\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:66-72\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 166\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 167\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 168\n      target_name: null\n    inputs:\n    - id: 164\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:101-145\n  - columns:\n    - !Single\n      name: null\n      target_id: 148\n      target_name: null\n    - !Single\n      name:\n      - employees\n      - employee_id\n      target_id: 149\n      target_name: null\n    - !Single\n      name:\n      - employees\n      - country\n      target_id: 150\n      target_name: null\n    inputs:\n    - id: 146\n      name: employees\n      table:\n      - default_db\n      - employees\n- - 1:148-154\n  - columns:\n    - !Single\n      name: null\n      target_id: 148\n      target_name: null\n    - !Single\n      name:\n      - employees\n      - employee_id\n      target_id: 149\n      target_name: null\n    - !Single\n      name:\n      - employees\n      - country\n      target_id: 150\n      target_name: null\n    inputs:\n    - id: 146\n      name: employees\n      table:\n      - default_db\n      - employees\n- - 1:73-156\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 166\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 167\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 168\n      target_name: null\n    inputs:\n    - id: 164\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 146\n      name: employees\n      table:\n      - default_db\n      - employees\n- - 1:189-233\n  - columns:\n    - !Single\n      name:\n      - invoice_items\n      - invoice_line_id\n      target_id: 127\n      target_name: null\n    - !Single\n      name:\n      - invoice_items\n      - invoice_id\n      target_id: 128\n      target_name: null\n    - !Single\n      name: null\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 125\n      name: invoice_items\n      table:\n      - default_db\n      - invoice_items\n- - 1:236-242\n  - columns:\n    - !Single\n      name:\n      - invoice_items\n      - invoice_line_id\n      target_id: 127\n      target_name: null\n    - !Single\n      name:\n      - invoice_items\n      - invoice_id\n      target_id: 128\n      target_name: null\n    - !Single\n      name: null\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 125\n      name: invoice_items\n      table:\n      - default_db\n      - invoice_items\n- - 1:157-244\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - customer_id\n      target_id: 166\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 167\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 168\n      target_name: null\n    inputs:\n    - id: 164\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 146\n      name: employees\n      table:\n      - default_db\n      - employees\n    - id: 125\n      name: invoice_items\n      table:\n      - default_db\n      - invoice_items\n- - 1:245-283\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 176\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 177\n      target_name: null\n    inputs:\n    - id: 164\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 146\n      name: employees\n      table:\n      - default_db\n      - employees\n    - id: 125\n      name: invoice_items\n      table:\n      - default_db\n      - invoice_items\nnodes:\n- id: 125\n  kind: Ident\n  span: 1:168-186\n  ident: !Ident\n  - default_db\n  - invoice_items\n  parent: 131\n- id: 127\n  kind: Ident\n  span: 1:198-213\n  ident: !Ident\n  - this\n  - invoice_items\n  - invoice_line_id\n  targets:\n  - 125\n  parent: 130\n- id: 128\n  kind: Ident\n  span: 1:215-225\n  ident: !Ident\n  - this\n  - invoice_items\n  - invoice_id\n  targets:\n  - 125\n  parent: 130\n- id: 129\n  kind: Literal\n  span: 1:227-231\n  parent: 130\n- id: 130\n  kind: Tuple\n  span: 1:196-233\n  children:\n  - 127\n  - 128\n  - 129\n  parent: 131\n- id: 131\n  kind: 'TransformCall: Select'\n  span: 1:189-233\n  children:\n  - 125\n  - 130\n  parent: 133\n- id: 133\n  kind: 'TransformCall: Take'\n  span: 1:236-242\n  children:\n  - 131\n  - 134\n  parent: 175\n- id: 134\n  kind: Literal\n  parent: 133\n- id: 146\n  kind: Ident\n  span: 1:84-98\n  ident: !Ident\n  - default_db\n  - employees\n  parent: 152\n- id: 148\n  kind: Ident\n  span: 1:110-121\n  ident: !Ident\n  - this\n  - employees\n  - employee_id\n  targets:\n  - 146\n  parent: 151\n- id: 149\n  kind: Ident\n  span: 1:123-134\n  ident: !Ident\n  - this\n  - employees\n  - employee_id\n  targets:\n  - 146\n  parent: 151\n- id: 150\n  kind: Ident\n  span: 1:136-143\n  ident: !Ident\n  - this\n  - employees\n  - country\n  targets:\n  - 146\n  parent: 151\n- id: 151\n  kind: Tuple\n  span: 1:108-145\n  children:\n  - 148\n  - 149\n  - 150\n  parent: 152\n- id: 152\n  kind: 'TransformCall: Select'\n  span: 1:101-145\n  children:\n  - 146\n  - 151\n  parent: 154\n- id: 154\n  kind: 'TransformCall: Take'\n  span: 1:148-154\n  children:\n  - 152\n  - 155\n  parent: 174\n- id: 155\n  kind: Literal\n  parent: 154\n- id: 164\n  kind: Ident\n  span: 1:0-13\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 170\n- id: 166\n  kind: Ident\n  span: 1:23-34\n  ident: !Ident\n  - this\n  - invoices\n  - customer_id\n  targets:\n  - 164\n  parent: 169\n- id: 167\n  kind: Ident\n  span: 1:36-46\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 164\n  parent: 169\n- id: 168\n  kind: Ident\n  span: 1:48-63\n  ident: !Ident\n  - this\n  - invoices\n  - billing_country\n  targets:\n  - 164\n  parent: 169\n- id: 169\n  kind: Tuple\n  span: 1:21-65\n  children:\n  - 166\n  - 167\n  - 168\n  parent: 170\n- id: 170\n  kind: 'TransformCall: Select'\n  span: 1:14-65\n  children:\n  - 164\n  - 169\n  parent: 172\n- id: 172\n  kind: 'TransformCall: Take'\n  span: 1:66-72\n  children:\n  - 170\n  - 173\n  parent: 174\n- id: 173\n  kind: Literal\n  parent: 172\n- id: 174\n  kind: 'TransformCall: Append'\n  span: 1:73-156\n  children:\n  - 172\n  - 154\n  parent: 175\n- id: 175\n  kind: 'TransformCall: Append'\n  span: 1:157-244\n  children:\n  - 174\n  - 133\n  parent: 179\n- id: 176\n  kind: Ident\n  span: 1:254-269\n  ident: !Ident\n  - this\n  - invoices\n  - billing_country\n  targets:\n  - 168\n  parent: 178\n- id: 177\n  kind: Ident\n  span: 1:271-281\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 167\n  parent: 178\n- id: 178\n  kind: Tuple\n  span: 1:252-283\n  children:\n  - 176\n  - 177\n  parent: 179\n- id: 179\n  kind: 'TransformCall: Select'\n  span: 1:245-283\n  children:\n  - 175\n  - 178\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:0-4\n              args:\n              - Ident:\n                - invoices\n                span: 1:5-13\n            span: 1:0-13\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:14-20\n              args:\n              - Tuple:\n                - Ident:\n                  - customer_id\n                  span: 1:23-34\n                - Ident:\n                  - invoice_id\n                  span: 1:36-46\n                - Ident:\n                  - billing_country\n                  span: 1:48-63\n                span: 1:21-65\n            span: 1:14-65\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:66-70\n              args:\n              - Literal:\n                  Integer: 5\n                span: 1:71-72\n            span: 1:66-72\n          - FuncCall:\n              name:\n                Ident:\n                - append\n                span: 1:73-79\n              args:\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - from\n                        span: 1:84-88\n                      args:\n                      - Ident:\n                        - employees\n                        span: 1:89-98\n                    span: 1:84-98\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - select\n                        span: 1:101-107\n                      args:\n                      - Tuple:\n                        - Ident:\n                          - employee_id\n                          span: 1:110-121\n                        - Ident:\n                          - employee_id\n                          span: 1:123-134\n                        - Ident:\n                          - country\n                          span: 1:136-143\n                        span: 1:108-145\n                    span: 1:101-145\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - take\n                        span: 1:148-152\n                      args:\n                      - Literal:\n                          Integer: 5\n                        span: 1:153-154\n                    span: 1:148-154\n                span: 1:84-154\n            span: 1:73-156\n          - FuncCall:\n              name:\n                Ident:\n                - append\n                span: 1:157-163\n              args:\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - from\n                        span: 1:168-172\n                      args:\n                      - Ident:\n                        - invoice_items\n                        span: 1:173-186\n                    span: 1:168-186\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - select\n                        span: 1:189-195\n                      args:\n                      - Tuple:\n                        - Ident:\n                          - invoice_line_id\n                          span: 1:198-213\n                        - Ident:\n                          - invoice_id\n                          span: 1:215-225\n                        - Literal: 'Null'\n                          span: 1:227-231\n                        span: 1:196-233\n                    span: 1:189-233\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - take\n                        span: 1:236-240\n                      args:\n                      - Literal:\n                          Integer: 5\n                        span: 1:241-242\n                    span: 1:236-242\n                span: 1:168-242\n            span: 1:157-244\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:245-251\n              args:\n              - Tuple:\n                - Ident:\n                  - billing_country\n                  span: 1:254-269\n                - Ident:\n                  - invoice_id\n                  span: 1:271-281\n                span: 1:252-283\n            span: 1:245-283\n        span: 1:0-283\n    span: 1:0-283\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__append_select_nulls.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect {an_id = invoice_id, name = null}\\ntake 2\\nappend (\\n  from employees\\n  select {an_id = null, name = first_name}\\n  take 2\\n)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_nulls.prql\n---\nframes:\n- - 1:14-54\n  - columns:\n    - !Single\n      name:\n      - an_id\n      target_id: 141\n      target_name: null\n    - !Single\n      name:\n      - name\n      target_id: 142\n      target_name: null\n    inputs:\n    - id: 139\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:55-61\n  - columns:\n    - !Single\n      name:\n      - an_id\n      target_id: 141\n      target_name: null\n    - !Single\n      name:\n      - name\n      target_id: 142\n      target_name: null\n    inputs:\n    - id: 139\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:90-130\n  - columns:\n    - !Single\n      name:\n      - an_id\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - name\n      target_id: 125\n      target_name: null\n    inputs:\n    - id: 122\n      name: employees\n      table:\n      - default_db\n      - employees\n- - 1:133-139\n  - columns:\n    - !Single\n      name:\n      - an_id\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - name\n      target_id: 125\n      target_name: null\n    inputs:\n    - id: 122\n      name: employees\n      table:\n      - default_db\n      - employees\n- - 1:62-141\n  - columns:\n    - !Single\n      name:\n      - an_id\n      target_id: 141\n      target_name: null\n    - !Single\n      name:\n      - name\n      target_id: 142\n      target_name: null\n    inputs:\n    - id: 139\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 122\n      name: employees\n      table:\n      - default_db\n      - employees\nnodes:\n- id: 122\n  kind: Ident\n  span: 1:73-87\n  ident: !Ident\n  - default_db\n  - employees\n  parent: 127\n- id: 124\n  kind: Literal\n  span: 1:106-110\n  alias: an_id\n  parent: 126\n- id: 125\n  kind: Ident\n  span: 1:119-129\n  alias: name\n  ident: !Ident\n  - this\n  - employees\n  - first_name\n  targets:\n  - 122\n  parent: 126\n- id: 126\n  kind: Tuple\n  span: 1:97-130\n  children:\n  - 124\n  - 125\n  parent: 127\n- id: 127\n  kind: 'TransformCall: Select'\n  span: 1:90-130\n  children:\n  - 122\n  - 126\n  parent: 129\n- id: 129\n  kind: 'TransformCall: Take'\n  span: 1:133-139\n  children:\n  - 127\n  - 130\n  parent: 148\n- id: 130\n  kind: Literal\n  parent: 129\n- id: 139\n  kind: Ident\n  span: 1:0-13\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 144\n- id: 141\n  kind: Ident\n  span: 1:30-40\n  alias: an_id\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 139\n  parent: 143\n- id: 142\n  kind: Literal\n  span: 1:49-53\n  alias: name\n  parent: 143\n- id: 143\n  kind: Tuple\n  span: 1:21-54\n  children:\n  - 141\n  - 142\n  parent: 144\n- id: 144\n  kind: 'TransformCall: Select'\n  span: 1:14-54\n  children:\n  - 139\n  - 143\n  parent: 146\n- id: 146\n  kind: 'TransformCall: Take'\n  span: 1:55-61\n  children:\n  - 144\n  - 147\n  parent: 148\n- id: 147\n  kind: Literal\n  parent: 146\n- id: 148\n  kind: 'TransformCall: Append'\n  span: 1:62-141\n  children:\n  - 146\n  - 129\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:0-4\n              args:\n              - Ident:\n                - invoices\n                span: 1:5-13\n            span: 1:0-13\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:14-20\n              args:\n              - Tuple:\n                - Ident:\n                  - invoice_id\n                  span: 1:30-40\n                  alias: an_id\n                - Literal: 'Null'\n                  span: 1:49-53\n                  alias: name\n                span: 1:21-54\n            span: 1:14-54\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:55-59\n              args:\n              - Literal:\n                  Integer: 2\n                span: 1:60-61\n            span: 1:55-61\n          - FuncCall:\n              name:\n                Ident:\n                - append\n                span: 1:62-68\n              args:\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - from\n                        span: 1:73-77\n                      args:\n                      - Ident:\n                        - employees\n                        span: 1:78-87\n                    span: 1:73-87\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - select\n                        span: 1:90-96\n                      args:\n                      - Tuple:\n                        - Literal: 'Null'\n                          span: 1:106-110\n                          alias: an_id\n                        - Ident:\n                          - first_name\n                          span: 1:119-129\n                          alias: name\n                        span: 1:97-130\n                    span: 1:90-130\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - take\n                        span: 1:133-137\n                      args:\n                      - Literal:\n                          Integer: 2\n                        span: 1:138-139\n                    span: 1:133-139\n                span: 1:73-139\n            span: 1:62-141\n        span: 1:0-141\n    span: 1:0-141\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__append_select_simple.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { invoice_id, billing_country }\\nappend (\\n  from invoices\\n  select { invoice_id = `invoice_id` + 100, billing_country }\\n)\\nfilter (billing_country | text.starts_with(\\\"I\\\"))\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_simple.prql\n---\nframes:\n- - 1:14-52\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 138\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 139\n      target_name: null\n    inputs:\n    - id: 136\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:80-139\n  - columns:\n    - !Single\n      name:\n      - invoice_id\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 128\n      target_name: null\n    inputs:\n    - id: 122\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:53-141\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 138\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 139\n      target_name: null\n    inputs:\n    - id: 136\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 122\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:142-190\n  - columns:\n    - !Single\n      name:\n      - invoices\n      - invoice_id\n      target_id: 138\n      target_name: null\n    - !Single\n      name:\n      - invoices\n      - billing_country\n      target_id: 139\n      target_name: null\n    inputs:\n    - id: 136\n      name: invoices\n      table:\n      - default_db\n      - invoices\n    - id: 122\n      name: invoices\n      table:\n      - default_db\n      - invoices\nnodes:\n- id: 122\n  kind: Ident\n  span: 1:64-77\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 130\n- id: 124\n  kind: RqOperator\n  span: 1:102-120\n  alias: invoice_id\n  targets:\n  - 126\n  - 127\n  parent: 129\n- id: 126\n  kind: Ident\n  span: 1:102-114\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 122\n- id: 127\n  kind: Literal\n  span: 1:117-120\n- id: 128\n  kind: Ident\n  span: 1:122-137\n  ident: !Ident\n  - this\n  - invoices\n  - billing_country\n  targets:\n  - 122\n  parent: 129\n- id: 129\n  kind: Tuple\n  span: 1:87-139\n  children:\n  - 124\n  - 128\n  parent: 130\n- id: 130\n  kind: 'TransformCall: Select'\n  span: 1:80-139\n  children:\n  - 122\n  - 129\n  parent: 142\n- id: 136\n  kind: Ident\n  span: 1:0-13\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 141\n- id: 138\n  kind: Ident\n  span: 1:23-33\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_id\n  targets:\n  - 136\n  parent: 140\n- id: 139\n  kind: Ident\n  span: 1:35-50\n  ident: !Ident\n  - this\n  - invoices\n  - billing_country\n  targets:\n  - 136\n  parent: 140\n- id: 140\n  kind: Tuple\n  span: 1:21-52\n  children:\n  - 138\n  - 139\n  parent: 141\n- id: 141\n  kind: 'TransformCall: Select'\n  span: 1:14-52\n  children:\n  - 136\n  - 140\n  parent: 142\n- id: 142\n  kind: 'TransformCall: Append'\n  span: 1:53-141\n  children:\n  - 141\n  - 130\n  parent: 148\n- id: 143\n  kind: RqOperator\n  span: 1:168-189\n  targets:\n  - 146\n  - 147\n  parent: 148\n- id: 146\n  kind: Literal\n  span: 1:185-188\n- id: 147\n  kind: Ident\n  span: 1:150-165\n  ident: !Ident\n  - this\n  - invoices\n  - billing_country\n  targets:\n  - 139\n- id: 148\n  kind: 'TransformCall: Filter'\n  span: 1:142-190\n  children:\n  - 142\n  - 143\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:0-4\n              args:\n              - Ident:\n                - invoices\n                span: 1:5-13\n            span: 1:0-13\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:14-20\n              args:\n              - Tuple:\n                - Ident:\n                  - invoice_id\n                  span: 1:23-33\n                - Ident:\n                  - billing_country\n                  span: 1:35-50\n                span: 1:21-52\n            span: 1:14-52\n          - FuncCall:\n              name:\n                Ident:\n                - append\n                span: 1:53-59\n              args:\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - from\n                        span: 1:64-68\n                      args:\n                      - Ident:\n                        - invoices\n                        span: 1:69-77\n                    span: 1:64-77\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - select\n                        span: 1:80-86\n                      args:\n                      - Tuple:\n                        - Binary:\n                            left:\n                              Ident:\n                              - invoice_id\n                              span: 1:102-114\n                            op: Add\n                            right:\n                              Literal:\n                                Integer: 100\n                              span: 1:117-120\n                          span: 1:102-120\n                          alias: invoice_id\n                        - Ident:\n                          - billing_country\n                          span: 1:122-137\n                        span: 1:87-139\n                    span: 1:80-139\n                span: 1:64-139\n            span: 1:53-141\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:142-148\n              args:\n              - Pipeline:\n                  exprs:\n                  - Ident:\n                    - billing_country\n                    span: 1:150-165\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - text\n                        - starts_with\n                        span: 1:168-184\n                      args:\n                      - Literal:\n                          String: I\n                        span: 1:185-188\n                    span: 1:168-189\n                span: 1:150-189\n            span: 1:142-190\n        span: 1:0-190\n    span: 1:0-190\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__arithmetic.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom [\\n    { id = 1, x_int =  13, x_float =  13.0, k_int =  5, k_float =  5.0 },\\n    { id = 2, x_int = -13, x_float = -13.0, k_int =  5, k_float =  5.0 },\\n    { id = 3, x_int =  13, x_float =  13.0, k_int = -5, k_float = -5.0 },\\n    { id = 4, x_int = -13, x_float = -13.0, k_int = -5, k_float = -5.0 },\\n]\\nselect {\\n    id,\\n\\n    x_int / k_int,\\n    x_int / k_float,\\n    x_float / k_int,\\n    x_float / k_float,\\n\\n    q_ii = x_int // k_int,\\n    q_if = x_int // k_float,\\n    q_fi = x_float // k_int,\\n    q_ff = x_float // k_float,\\n\\n    r_ii = x_int % k_int,\\n    r_if = x_int % k_float,\\n    r_fi = x_float % k_int,\\n    r_ff = x_float % k_float,\\n\\n    (q_ii * k_int + r_ii | math.round 0),\\n    (q_if * k_float + r_if | math.round 0),\\n    (q_fi * k_int + r_fi | math.round 0),\\n    (q_ff * k_float + r_ff | math.round 0),\\n}\\nsort id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/arithmetic.prql\n---\nframes:\n- - 1:318-824\n  - columns:\n    - !Single\n      name:\n      - _literal_119\n      - id\n      target_id: 161\n      target_name: null\n    - !Single\n      name: null\n      target_id: 162\n      target_name: null\n    - !Single\n      name: null\n      target_id: 166\n      target_name: null\n    - !Single\n      name: null\n      target_id: 170\n      target_name: null\n    - !Single\n      name: null\n      target_id: 174\n      target_name: null\n    - !Single\n      name:\n      - q_ii\n      target_id: 178\n      target_name: null\n    - !Single\n      name:\n      - q_if\n      target_id: 182\n      target_name: null\n    - !Single\n      name:\n      - q_fi\n      target_id: 186\n      target_name: null\n    - !Single\n      name:\n      - q_ff\n      target_id: 190\n      target_name: null\n    - !Single\n      name:\n      - r_ii\n      target_id: 194\n      target_name: null\n    - !Single\n      name:\n      - r_if\n      target_id: 198\n      target_name: null\n    - !Single\n      name:\n      - r_fi\n      target_id: 202\n      target_name: null\n    - !Single\n      name:\n      - r_ff\n      target_id: 206\n      target_name: null\n    - !Single\n      name: null\n      target_id: 210\n      target_name: null\n    - !Single\n      name: null\n      target_id: 221\n      target_name: null\n    - !Single\n      name: null\n      target_id: 232\n      target_name: null\n    - !Single\n      name: null\n      target_id: 243\n      target_name: null\n    inputs:\n    - id: 119\n      name: _literal_119\n      table:\n      - default_db\n      - _literal_119\n- - 1:825-832\n  - columns:\n    - !Single\n      name:\n      - _literal_119\n      - id\n      target_id: 161\n      target_name: null\n    - !Single\n      name: null\n      target_id: 162\n      target_name: null\n    - !Single\n      name: null\n      target_id: 166\n      target_name: null\n    - !Single\n      name: null\n      target_id: 170\n      target_name: null\n    - !Single\n      name: null\n      target_id: 174\n      target_name: null\n    - !Single\n      name:\n      - q_ii\n      target_id: 178\n      target_name: null\n    - !Single\n      name:\n      - q_if\n      target_id: 182\n      target_name: null\n    - !Single\n      name:\n      - q_fi\n      target_id: 186\n      target_name: null\n    - !Single\n      name:\n      - q_ff\n      target_id: 190\n      target_name: null\n    - !Single\n      name:\n      - r_ii\n      target_id: 194\n      target_name: null\n    - !Single\n      name:\n      - r_if\n      target_id: 198\n      target_name: null\n    - !Single\n      name:\n      - r_fi\n      target_id: 202\n      target_name: null\n    - !Single\n      name:\n      - r_ff\n      target_id: 206\n      target_name: null\n    - !Single\n      name: null\n      target_id: 210\n      target_name: null\n    - !Single\n      name: null\n      target_id: 221\n      target_name: null\n    - !Single\n      name: null\n      target_id: 232\n      target_name: null\n    - !Single\n      name: null\n      target_id: 243\n      target_name: null\n    inputs:\n    - id: 119\n      name: _literal_119\n      table:\n      - default_db\n      - _literal_119\nnodes:\n- id: 119\n  kind: Array\n  span: 1:13-317\n  children:\n  - 120\n  - 126\n  - 136\n  - 146\n  parent: 255\n- id: 120\n  kind: Tuple\n  span: 1:24-92\n  children:\n  - 121\n  - 122\n  - 123\n  - 124\n  - 125\n  parent: 119\n- id: 121\n  kind: Literal\n  span: 1:31-32\n  alias: id\n  parent: 120\n- id: 122\n  kind: Literal\n  span: 1:43-45\n  alias: x_int\n  parent: 120\n- id: 123\n  kind: Literal\n  span: 1:58-62\n  alias: x_float\n  parent: 120\n- id: 124\n  kind: Literal\n  span: 1:73-74\n  alias: k_int\n  parent: 120\n- id: 125\n  kind: Literal\n  span: 1:87-90\n  alias: k_float\n  parent: 120\n- id: 126\n  kind: Tuple\n  span: 1:98-166\n  children:\n  - 127\n  - 128\n  - 131\n  - 134\n  - 135\n  parent: 119\n- id: 127\n  kind: Literal\n  span: 1:105-106\n  alias: id\n  parent: 126\n- id: 128\n  kind: Literal\n  span: 1:116-119\n  alias: x_int\n  parent: 126\n- id: 131\n  kind: Literal\n  span: 1:131-136\n  alias: x_float\n  parent: 126\n- id: 134\n  kind: Literal\n  span: 1:147-148\n  alias: k_int\n  parent: 126\n- id: 135\n  kind: Literal\n  span: 1:161-164\n  alias: k_float\n  parent: 126\n- id: 136\n  kind: Tuple\n  span: 1:172-240\n  children:\n  - 137\n  - 138\n  - 139\n  - 140\n  - 143\n  parent: 119\n- id: 137\n  kind: Literal\n  span: 1:179-180\n  alias: id\n  parent: 136\n- id: 138\n  kind: Literal\n  span: 1:191-193\n  alias: x_int\n  parent: 136\n- id: 139\n  kind: Literal\n  span: 1:206-210\n  alias: x_float\n  parent: 136\n- id: 140\n  kind: Literal\n  span: 1:220-222\n  alias: k_int\n  parent: 136\n- id: 143\n  kind: Literal\n  span: 1:234-238\n  alias: k_float\n  parent: 136\n- id: 146\n  kind: Tuple\n  span: 1:246-314\n  children:\n  - 147\n  - 148\n  - 151\n  - 154\n  - 157\n  parent: 119\n- id: 147\n  kind: Literal\n  span: 1:253-254\n  alias: id\n  parent: 146\n- id: 148\n  kind: Literal\n  span: 1:264-267\n  alias: x_int\n  parent: 146\n- id: 151\n  kind: Literal\n  span: 1:279-284\n  alias: x_float\n  parent: 146\n- id: 154\n  kind: Literal\n  span: 1:294-296\n  alias: k_int\n  parent: 146\n- id: 157\n  kind: Literal\n  span: 1:308-312\n  alias: k_float\n  parent: 146\n- id: 161\n  kind: Ident\n  span: 1:331-333\n  ident: !Ident\n  - this\n  - _literal_119\n  - id\n  targets:\n  - 119\n  parent: 254\n- id: 162\n  kind: RqOperator\n  span: 1:340-353\n  targets:\n  - 164\n  - 165\n  parent: 254\n- id: 164\n  kind: Ident\n  span: 1:340-345\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_int\n  targets:\n  - 119\n- id: 165\n  kind: Ident\n  span: 1:348-353\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_int\n  targets:\n  - 119\n- id: 166\n  kind: RqOperator\n  span: 1:359-374\n  targets:\n  - 168\n  - 169\n  parent: 254\n- id: 168\n  kind: Ident\n  span: 1:359-364\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_int\n  targets:\n  - 119\n- id: 169\n  kind: Ident\n  span: 1:367-374\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_float\n  targets:\n  - 119\n- id: 170\n  kind: RqOperator\n  span: 1:380-395\n  targets:\n  - 172\n  - 173\n  parent: 254\n- id: 172\n  kind: Ident\n  span: 1:380-387\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_float\n  targets:\n  - 119\n- id: 173\n  kind: Ident\n  span: 1:390-395\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_int\n  targets:\n  - 119\n- id: 174\n  kind: RqOperator\n  span: 1:401-418\n  targets:\n  - 176\n  - 177\n  parent: 254\n- id: 176\n  kind: Ident\n  span: 1:401-408\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_float\n  targets:\n  - 119\n- id: 177\n  kind: Ident\n  span: 1:411-418\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_float\n  targets:\n  - 119\n- id: 178\n  kind: RqOperator\n  span: 1:432-446\n  alias: q_ii\n  targets:\n  - 180\n  - 181\n  parent: 254\n- id: 180\n  kind: Ident\n  span: 1:432-437\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_int\n  targets:\n  - 119\n- id: 181\n  kind: Ident\n  span: 1:441-446\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_int\n  targets:\n  - 119\n- id: 182\n  kind: RqOperator\n  span: 1:459-475\n  alias: q_if\n  targets:\n  - 184\n  - 185\n  parent: 254\n- id: 184\n  kind: Ident\n  span: 1:459-464\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_int\n  targets:\n  - 119\n- id: 185\n  kind: Ident\n  span: 1:468-475\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_float\n  targets:\n  - 119\n- id: 186\n  kind: RqOperator\n  span: 1:488-504\n  alias: q_fi\n  targets:\n  - 188\n  - 189\n  parent: 254\n- id: 188\n  kind: Ident\n  span: 1:488-495\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_float\n  targets:\n  - 119\n- id: 189\n  kind: Ident\n  span: 1:499-504\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_int\n  targets:\n  - 119\n- id: 190\n  kind: RqOperator\n  span: 1:517-535\n  alias: q_ff\n  targets:\n  - 192\n  - 193\n  parent: 254\n- id: 192\n  kind: Ident\n  span: 1:517-524\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_float\n  targets:\n  - 119\n- id: 193\n  kind: Ident\n  span: 1:528-535\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_float\n  targets:\n  - 119\n- id: 194\n  kind: RqOperator\n  span: 1:549-562\n  alias: r_ii\n  targets:\n  - 196\n  - 197\n  parent: 254\n- id: 196\n  kind: Ident\n  span: 1:549-554\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_int\n  targets:\n  - 119\n- id: 197\n  kind: Ident\n  span: 1:557-562\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_int\n  targets:\n  - 119\n- id: 198\n  kind: RqOperator\n  span: 1:575-590\n  alias: r_if\n  targets:\n  - 200\n  - 201\n  parent: 254\n- id: 200\n  kind: Ident\n  span: 1:575-580\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_int\n  targets:\n  - 119\n- id: 201\n  kind: Ident\n  span: 1:583-590\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_float\n  targets:\n  - 119\n- id: 202\n  kind: RqOperator\n  span: 1:603-618\n  alias: r_fi\n  targets:\n  - 204\n  - 205\n  parent: 254\n- id: 204\n  kind: Ident\n  span: 1:603-610\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_float\n  targets:\n  - 119\n- id: 205\n  kind: Ident\n  span: 1:613-618\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_int\n  targets:\n  - 119\n- id: 206\n  kind: RqOperator\n  span: 1:631-648\n  alias: r_ff\n  targets:\n  - 208\n  - 209\n  parent: 254\n- id: 208\n  kind: Ident\n  span: 1:631-638\n  ident: !Ident\n  - this\n  - _literal_119\n  - x_float\n  targets:\n  - 119\n- id: 209\n  kind: Ident\n  span: 1:641-648\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_float\n  targets:\n  - 119\n- id: 210\n  kind: RqOperator\n  span: 1:678-690\n  targets:\n  - 213\n  - 214\n  parent: 254\n- id: 213\n  kind: Literal\n  span: 1:689-690\n- id: 214\n  kind: RqOperator\n  span: 1:656-675\n  targets:\n  - 216\n  - 220\n- id: 216\n  kind: RqOperator\n  span: 1:656-668\n  targets:\n  - 218\n  - 219\n- id: 218\n  kind: Ident\n  span: 1:656-660\n  ident: !Ident\n  - this\n  - q_ii\n  targets:\n  - 178\n- id: 219\n  kind: Ident\n  span: 1:663-668\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_int\n  targets:\n  - 119\n- id: 220\n  kind: Ident\n  span: 1:671-675\n  ident: !Ident\n  - this\n  - r_ii\n  targets:\n  - 194\n- id: 221\n  kind: RqOperator\n  span: 1:722-734\n  targets:\n  - 224\n  - 225\n  parent: 254\n- id: 224\n  kind: Literal\n  span: 1:733-734\n- id: 225\n  kind: RqOperator\n  span: 1:698-719\n  targets:\n  - 227\n  - 231\n- id: 227\n  kind: RqOperator\n  span: 1:698-712\n  targets:\n  - 229\n  - 230\n- id: 229\n  kind: Ident\n  span: 1:698-702\n  ident: !Ident\n  - this\n  - q_if\n  targets:\n  - 182\n- id: 230\n  kind: Ident\n  span: 1:705-712\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_float\n  targets:\n  - 119\n- id: 231\n  kind: Ident\n  span: 1:715-719\n  ident: !Ident\n  - this\n  - r_if\n  targets:\n  - 198\n- id: 232\n  kind: RqOperator\n  span: 1:764-776\n  targets:\n  - 235\n  - 236\n  parent: 254\n- id: 235\n  kind: Literal\n  span: 1:775-776\n- id: 236\n  kind: RqOperator\n  span: 1:742-761\n  targets:\n  - 238\n  - 242\n- id: 238\n  kind: RqOperator\n  span: 1:742-754\n  targets:\n  - 240\n  - 241\n- id: 240\n  kind: Ident\n  span: 1:742-746\n  ident: !Ident\n  - this\n  - q_fi\n  targets:\n  - 186\n- id: 241\n  kind: Ident\n  span: 1:749-754\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_int\n  targets:\n  - 119\n- id: 242\n  kind: Ident\n  span: 1:757-761\n  ident: !Ident\n  - this\n  - r_fi\n  targets:\n  - 202\n- id: 243\n  kind: RqOperator\n  span: 1:808-820\n  targets:\n  - 246\n  - 247\n  parent: 254\n- id: 246\n  kind: Literal\n  span: 1:819-820\n- id: 247\n  kind: RqOperator\n  span: 1:784-805\n  targets:\n  - 249\n  - 253\n- id: 249\n  kind: RqOperator\n  span: 1:784-798\n  targets:\n  - 251\n  - 252\n- id: 251\n  kind: Ident\n  span: 1:784-788\n  ident: !Ident\n  - this\n  - q_ff\n  targets:\n  - 190\n- id: 252\n  kind: Ident\n  span: 1:791-798\n  ident: !Ident\n  - this\n  - _literal_119\n  - k_float\n  targets:\n  - 119\n- id: 253\n  kind: Ident\n  span: 1:801-805\n  ident: !Ident\n  - this\n  - r_ff\n  targets:\n  - 206\n- id: 254\n  kind: Tuple\n  span: 1:325-824\n  children:\n  - 161\n  - 162\n  - 166\n  - 170\n  - 174\n  - 178\n  - 182\n  - 186\n  - 190\n  - 194\n  - 198\n  - 202\n  - 206\n  - 210\n  - 221\n  - 232\n  - 243\n  parent: 255\n- id: 255\n  kind: 'TransformCall: Select'\n  span: 1:318-824\n  children:\n  - 119\n  - 254\n  parent: 258\n- id: 256\n  kind: Ident\n  span: 1:830-832\n  ident: !Ident\n  - this\n  - _literal_119\n  - id\n  targets:\n  - 161\n  parent: 258\n- id: 258\n  kind: 'TransformCall: Sort'\n  span: 1:825-832\n  children:\n  - 255\n  - 256\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:13-17\n              args:\n              - Array:\n                - Tuple:\n                  - Literal:\n                      Integer: 1\n                    span: 1:31-32\n                    alias: id\n                  - Literal:\n                      Integer: 13\n                    span: 1:43-45\n                    alias: x_int\n                  - Literal:\n                      Float: 13.0\n                    span: 1:58-62\n                    alias: x_float\n                  - Literal:\n                      Integer: 5\n                    span: 1:73-74\n                    alias: k_int\n                  - Literal:\n                      Float: 5.0\n                    span: 1:87-90\n                    alias: k_float\n                  span: 1:24-92\n                - Tuple:\n                  - Literal:\n                      Integer: 2\n                    span: 1:105-106\n                    alias: id\n                  - Unary:\n                      op: Neg\n                      expr:\n                        Literal:\n                          Integer: 13\n                        span: 1:117-119\n                    span: 1:116-119\n                    alias: x_int\n                  - Unary:\n                      op: Neg\n                      expr:\n                        Literal:\n                          Float: 13.0\n                        span: 1:132-136\n                    span: 1:131-136\n                    alias: x_float\n                  - Literal:\n                      Integer: 5\n                    span: 1:147-148\n                    alias: k_int\n                  - Literal:\n                      Float: 5.0\n                    span: 1:161-164\n                    alias: k_float\n                  span: 1:98-166\n                - Tuple:\n                  - Literal:\n                      Integer: 3\n                    span: 1:179-180\n                    alias: id\n                  - Literal:\n                      Integer: 13\n                    span: 1:191-193\n                    alias: x_int\n                  - Literal:\n                      Float: 13.0\n                    span: 1:206-210\n                    alias: x_float\n                  - Unary:\n                      op: Neg\n                      expr:\n                        Literal:\n                          Integer: 5\n                        span: 1:221-222\n                    span: 1:220-222\n                    alias: k_int\n                  - Unary:\n                      op: Neg\n                      expr:\n                        Literal:\n                          Float: 5.0\n                        span: 1:235-238\n                    span: 1:234-238\n                    alias: k_float\n                  span: 1:172-240\n                - Tuple:\n                  - Literal:\n                      Integer: 4\n                    span: 1:253-254\n                    alias: id\n                  - Unary:\n                      op: Neg\n                      expr:\n                        Literal:\n                          Integer: 13\n                        span: 1:265-267\n                    span: 1:264-267\n                    alias: x_int\n                  - Unary:\n                      op: Neg\n                      expr:\n                        Literal:\n                          Float: 13.0\n                        span: 1:280-284\n                    span: 1:279-284\n                    alias: x_float\n                  - Unary:\n                      op: Neg\n                      expr:\n                        Literal:\n                          Integer: 5\n                        span: 1:295-296\n                    span: 1:294-296\n                    alias: k_int\n                  - Unary:\n                      op: Neg\n                      expr:\n                        Literal:\n                          Float: 5.0\n                        span: 1:309-312\n                    span: 1:308-312\n                    alias: k_float\n                  span: 1:246-314\n                span: 1:18-317\n            span: 1:13-317\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:318-324\n              args:\n              - Tuple:\n                - Ident:\n                  - id\n                  span: 1:331-333\n                - Binary:\n                    left:\n                      Ident:\n                      - x_int\n                      span: 1:340-345\n                    op: DivFloat\n                    right:\n                      Ident:\n                      - k_int\n                      span: 1:348-353\n                  span: 1:340-353\n                - Binary:\n                    left:\n                      Ident:\n                      - x_int\n                      span: 1:359-364\n                    op: DivFloat\n                    right:\n                      Ident:\n                      - k_float\n                      span: 1:367-374\n                  span: 1:359-374\n                - Binary:\n                    left:\n                      Ident:\n                      - x_float\n                      span: 1:380-387\n                    op: DivFloat\n                    right:\n                      Ident:\n                      - k_int\n                      span: 1:390-395\n                  span: 1:380-395\n                - Binary:\n                    left:\n                      Ident:\n                      - x_float\n                      span: 1:401-408\n                    op: DivFloat\n                    right:\n                      Ident:\n                      - k_float\n                      span: 1:411-418\n                  span: 1:401-418\n                - Binary:\n                    left:\n                      Ident:\n                      - x_int\n                      span: 1:432-437\n                    op: DivInt\n                    right:\n                      Ident:\n                      - k_int\n                      span: 1:441-446\n                  span: 1:432-446\n                  alias: q_ii\n                - Binary:\n                    left:\n                      Ident:\n                      - x_int\n                      span: 1:459-464\n                    op: DivInt\n                    right:\n                      Ident:\n                      - k_float\n                      span: 1:468-475\n                  span: 1:459-475\n                  alias: q_if\n                - Binary:\n                    left:\n                      Ident:\n                      - x_float\n                      span: 1:488-495\n                    op: DivInt\n                    right:\n                      Ident:\n                      - k_int\n                      span: 1:499-504\n                  span: 1:488-504\n                  alias: q_fi\n                - Binary:\n                    left:\n                      Ident:\n                      - x_float\n                      span: 1:517-524\n                    op: DivInt\n                    right:\n                      Ident:\n                      - k_float\n                      span: 1:528-535\n                  span: 1:517-535\n                  alias: q_ff\n                - Binary:\n                    left:\n                      Ident:\n                      - x_int\n                      span: 1:549-554\n                    op: Mod\n                    right:\n                      Ident:\n                      - k_int\n                      span: 1:557-562\n                  span: 1:549-562\n                  alias: r_ii\n                - Binary:\n                    left:\n                      Ident:\n                      - x_int\n                      span: 1:575-580\n                    op: Mod\n                    right:\n                      Ident:\n                      - k_float\n                      span: 1:583-590\n                  span: 1:575-590\n                  alias: r_if\n                - Binary:\n                    left:\n                      Ident:\n                      - x_float\n                      span: 1:603-610\n                    op: Mod\n                    right:\n                      Ident:\n                      - k_int\n                      span: 1:613-618\n                  span: 1:603-618\n                  alias: r_fi\n                - Binary:\n                    left:\n                      Ident:\n                      - x_float\n                      span: 1:631-638\n                    op: Mod\n                    right:\n                      Ident:\n                      - k_float\n                      span: 1:641-648\n                  span: 1:631-648\n                  alias: r_ff\n                - Pipeline:\n                    exprs:\n                    - Binary:\n                        left:\n                          Binary:\n                            left:\n                              Ident:\n                              - q_ii\n                              span: 1:656-660\n                            op: Mul\n                            right:\n                              Ident:\n                              - k_int\n                              span: 1:663-668\n                          span: 1:656-668\n                        op: Add\n                        right:\n                          Ident:\n                          - r_ii\n                          span: 1:671-675\n                      span: 1:656-675\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:678-688\n                        args:\n                        - Literal:\n                            Integer: 0\n                          span: 1:689-690\n                      span: 1:678-690\n                  span: 1:655-691\n                - Pipeline:\n                    exprs:\n                    - Binary:\n                        left:\n                          Binary:\n                            left:\n                              Ident:\n                              - q_if\n                              span: 1:698-702\n                            op: Mul\n                            right:\n                              Ident:\n                              - k_float\n                              span: 1:705-712\n                          span: 1:698-712\n                        op: Add\n                        right:\n                          Ident:\n                          - r_if\n                          span: 1:715-719\n                      span: 1:698-719\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:722-732\n                        args:\n                        - Literal:\n                            Integer: 0\n                          span: 1:733-734\n                      span: 1:722-734\n                  span: 1:697-735\n                - Pipeline:\n                    exprs:\n                    - Binary:\n                        left:\n                          Binary:\n                            left:\n                              Ident:\n                              - q_fi\n                              span: 1:742-746\n                            op: Mul\n                            right:\n                              Ident:\n                              - k_int\n                              span: 1:749-754\n                          span: 1:742-754\n                        op: Add\n                        right:\n                          Ident:\n                          - r_fi\n                          span: 1:757-761\n                      span: 1:742-761\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:764-774\n                        args:\n                        - Literal:\n                            Integer: 0\n                          span: 1:775-776\n                      span: 1:764-776\n                  span: 1:741-777\n                - Pipeline:\n                    exprs:\n                    - Binary:\n                        left:\n                          Binary:\n                            left:\n                              Ident:\n                              - q_ff\n                              span: 1:784-788\n                            op: Mul\n                            right:\n                              Ident:\n                              - k_float\n                              span: 1:791-798\n                          span: 1:784-798\n                        op: Add\n                        right:\n                          Ident:\n                          - r_ff\n                          span: 1:801-805\n                      span: 1:784-805\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:808-818\n                        args:\n                        - Literal:\n                            Integer: 0\n                          span: 1:819-820\n                      span: 1:808-820\n                  span: 1:783-821\n                span: 1:325-824\n            span: 1:318-824\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:825-829\n              args:\n              - Ident:\n                - id\n                span: 1:830-832\n            span: 1:825-832\n        span: 1:13-832\n    span: 1:0-832\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__cast.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {-bytes}\\nselect {\\n    name,\\n    bin = ((album_id | as REAL) * 99)\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/cast.prql\n---\nframes:\n- - 1:25-38\n  - columns:\n    - !All\n      input_id: 122\n      except: []\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:39-97\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - name\n      target_id: 129\n      target_name: null\n    - !Single\n      name:\n      - bin\n      target_id: 130\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:98-105\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - name\n      target_id: 129\n      target_name: null\n    - !Single\n      name:\n      - bin\n      target_id: 130\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 122\n  kind: Ident\n  span: 1:13-24\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 128\n- id: 126\n  kind: Ident\n  span: 1:32-37\n  ident: !Ident\n  - this\n  - tracks\n  - bytes\n  targets:\n  - 122\n  parent: 128\n- id: 128\n  kind: 'TransformCall: Sort'\n  span: 1:25-38\n  children:\n  - 122\n  - 126\n  parent: 138\n- id: 129\n  kind: Ident\n  span: 1:52-56\n  ident: !Ident\n  - this\n  - tracks\n  - name\n  targets:\n  - 122\n  parent: 137\n- id: 130\n  kind: RqOperator\n  span: 1:68-95\n  alias: bin\n  targets:\n  - 132\n  - 136\n  parent: 137\n- id: 132\n  kind: RqOperator\n  span: 1:81-88\n  targets:\n  - 135\n- id: 135\n  kind: Ident\n  span: 1:70-78\n  ident: !Ident\n  - this\n  - tracks\n  - album_id\n  targets:\n  - 122\n- id: 136\n  kind: Literal\n  span: 1:92-94\n- id: 137\n  kind: Tuple\n  span: 1:46-97\n  children:\n  - 129\n  - 130\n  parent: 138\n- id: 138\n  kind: 'TransformCall: Select'\n  span: 1:39-97\n  children:\n  - 128\n  - 137\n  parent: 140\n- id: 140\n  kind: 'TransformCall: Take'\n  span: 1:98-105\n  children:\n  - 138\n  - 141\n- id: 141\n  kind: Literal\n  parent: 140\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:13-17\n              args:\n              - Ident:\n                - tracks\n                span: 1:18-24\n            span: 1:13-24\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:25-29\n              args:\n              - Tuple:\n                - Unary:\n                    op: Neg\n                    expr:\n                      Ident:\n                      - bytes\n                      span: 1:32-37\n                  span: 1:31-37\n                span: 1:30-38\n            span: 1:25-38\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:39-45\n              args:\n              - Tuple:\n                - Ident:\n                  - name\n                  span: 1:52-56\n                - Binary:\n                    left:\n                      Pipeline:\n                        exprs:\n                        - Ident:\n                          - album_id\n                          span: 1:70-78\n                        - FuncCall:\n                            name:\n                              Ident:\n                              - as\n                              span: 1:81-83\n                            args:\n                            - Ident:\n                              - REAL\n                              span: 1:84-88\n                          span: 1:81-88\n                      span: 1:70-88\n                    op: Mul\n                    right:\n                      Literal:\n                        Integer: 99\n                      span: 1:92-94\n                  span: 1:68-95\n                  alias: bin\n                span: 1:46-97\n            span: 1:39-97\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:98-102\n              args:\n              - Literal:\n                  Integer: 20\n                span: 1:103-105\n            span: 1:98-105\n        span: 1:13-105\n    span: 1:0-105\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__constants_only.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from genres\\ntake 10\\nfilter true\\ntake 20\\nfilter true\\nselect d = 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/constants_only.prql\n---\nframes:\n- - 1:12-19\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    inputs:\n    - id: 128\n      name: genres\n      table:\n      - default_db\n      - genres\n- - 1:20-31\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    inputs:\n    - id: 128\n      name: genres\n      table:\n      - default_db\n      - genres\n- - 1:32-39\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    inputs:\n    - id: 128\n      name: genres\n      table:\n      - default_db\n      - genres\n- - 1:40-51\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    inputs:\n    - id: 128\n      name: genres\n      table:\n      - default_db\n      - genres\n- - 1:52-65\n  - columns:\n    - !Single\n      name:\n      - d\n      target_id: 140\n      target_name: null\n    inputs:\n    - id: 128\n      name: genres\n      table:\n      - default_db\n      - genres\nnodes:\n- id: 128\n  kind: Ident\n  span: 1:0-11\n  ident: !Ident\n  - default_db\n  - genres\n  parent: 131\n- id: 131\n  kind: 'TransformCall: Take'\n  span: 1:12-19\n  children:\n  - 128\n  - 132\n  parent: 134\n- id: 132\n  kind: Literal\n  parent: 131\n- id: 133\n  kind: Literal\n  span: 1:27-31\n  parent: 134\n- id: 134\n  kind: 'TransformCall: Filter'\n  span: 1:20-31\n  children:\n  - 131\n  - 133\n  parent: 136\n- id: 136\n  kind: 'TransformCall: Take'\n  span: 1:32-39\n  children:\n  - 134\n  - 137\n  parent: 139\n- id: 137\n  kind: Literal\n  parent: 136\n- id: 138\n  kind: Literal\n  span: 1:47-51\n  parent: 139\n- id: 139\n  kind: 'TransformCall: Filter'\n  span: 1:40-51\n  children:\n  - 136\n  - 138\n  parent: 142\n- id: 140\n  kind: Literal\n  span: 1:63-65\n  alias: d\n  parent: 141\n- id: 141\n  kind: Tuple\n  span: 1:63-65\n  children:\n  - 140\n  parent: 142\n- id: 142\n  kind: 'TransformCall: Select'\n  span: 1:52-65\n  children:\n  - 139\n  - 141\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:0-4\n              args:\n              - Ident:\n                - genres\n                span: 1:5-11\n            span: 1:0-11\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:12-16\n              args:\n              - Literal:\n                  Integer: 10\n                span: 1:17-19\n            span: 1:12-19\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:20-26\n              args:\n              - Literal:\n                  Boolean: true\n                span: 1:27-31\n            span: 1:20-31\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:32-36\n              args:\n              - Literal:\n                  Integer: 20\n                span: 1:37-39\n            span: 1:32-39\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:40-46\n              args:\n              - Literal:\n                  Boolean: true\n                span: 1:47-51\n            span: 1:40-51\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:52-58\n              args:\n              - Literal:\n                  Integer: 10\n                span: 1:63-65\n                alias: d\n            span: 1:52-65\n        span: 1:0-65\n    span: 1:0-65\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__date_to_text.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# generic:skip\\n# glaredb:skip\\n# sqlite:skip\\n# mssql:test\\nfrom invoices\\ntake 20\\nselect {\\n    d1 = (invoice_date | date.to_text \\\"%Y/%m/%d\\\"),\\n    d2 = (invoice_date | date.to_text \\\"%F\\\"),\\n    d3 = (invoice_date | date.to_text \\\"%D\\\"),\\n    d4 = (invoice_date | date.to_text \\\"%H:%M:%S.%f\\\"),\\n    d5 = (invoice_date | date.to_text \\\"%r\\\"),\\n    d6 = (invoice_date | date.to_text \\\"%A %B %-d %Y\\\"),\\n    d7 = (invoice_date | date.to_text \\\"%a, %-d %b %Y at %I:%M:%S %p\\\"),\\n    d8 = (invoice_date | date.to_text \\\"%+\\\"),\\n    d9 = (invoice_date | date.to_text \\\"%-d/%-m/%y\\\"),\\n    d10 = (invoice_date | date.to_text \\\"%-Hh %Mmin\\\"),\\n    d11 = (invoice_date | date.to_text \\\"%M'%S\\\\\\\"\\\"),\\n    d12 = (invoice_date | date.to_text \\\"100%% in %d days\\\"),\\n}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/date_to_text.prql\n---\nframes:\n- - 1:71-78\n  - columns:\n    - !All\n      input_id: 119\n      except: []\n    inputs:\n    - id: 119\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:79-718\n  - columns:\n    - !Single\n      name:\n      - d1\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - d2\n      target_id: 129\n      target_name: null\n    - !Single\n      name:\n      - d3\n      target_id: 134\n      target_name: null\n    - !Single\n      name:\n      - d4\n      target_id: 139\n      target_name: null\n    - !Single\n      name:\n      - d5\n      target_id: 144\n      target_name: null\n    - !Single\n      name:\n      - d6\n      target_id: 149\n      target_name: null\n    - !Single\n      name:\n      - d7\n      target_id: 154\n      target_name: null\n    - !Single\n      name:\n      - d8\n      target_id: 159\n      target_name: null\n    - !Single\n      name:\n      - d9\n      target_id: 164\n      target_name: null\n    - !Single\n      name:\n      - d10\n      target_id: 169\n      target_name: null\n    - !Single\n      name:\n      - d11\n      target_id: 174\n      target_name: null\n    - !Single\n      name:\n      - d12\n      target_id: 179\n      target_name: null\n    inputs:\n    - id: 119\n      name: invoices\n      table:\n      - default_db\n      - invoices\nnodes:\n- id: 119\n  kind: Ident\n  span: 1:57-70\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 122\n- id: 122\n  kind: 'TransformCall: Take'\n  span: 1:71-78\n  children:\n  - 119\n  - 123\n  parent: 185\n- id: 123\n  kind: Literal\n  parent: 122\n- id: 124\n  kind: RqOperator\n  span: 1:113-136\n  alias: d1\n  targets:\n  - 127\n  - 128\n  parent: 184\n- id: 127\n  kind: Literal\n  span: 1:126-136\n- id: 128\n  kind: Ident\n  span: 1:98-110\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 129\n  kind: RqOperator\n  span: 1:164-181\n  alias: d2\n  targets:\n  - 132\n  - 133\n  parent: 184\n- id: 132\n  kind: Literal\n  span: 1:177-181\n- id: 133\n  kind: Ident\n  span: 1:149-161\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 134\n  kind: RqOperator\n  span: 1:209-226\n  alias: d3\n  targets:\n  - 137\n  - 138\n  parent: 184\n- id: 137\n  kind: Literal\n  span: 1:222-226\n- id: 138\n  kind: Ident\n  span: 1:194-206\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 139\n  kind: RqOperator\n  span: 1:254-280\n  alias: d4\n  targets:\n  - 142\n  - 143\n  parent: 184\n- id: 142\n  kind: Literal\n  span: 1:267-280\n- id: 143\n  kind: Ident\n  span: 1:239-251\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 144\n  kind: RqOperator\n  span: 1:308-325\n  alias: d5\n  targets:\n  - 147\n  - 148\n  parent: 184\n- id: 147\n  kind: Literal\n  span: 1:321-325\n- id: 148\n  kind: Ident\n  span: 1:293-305\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 149\n  kind: RqOperator\n  span: 1:353-380\n  alias: d6\n  targets:\n  - 152\n  - 153\n  parent: 184\n- id: 152\n  kind: Literal\n  span: 1:366-380\n- id: 153\n  kind: Ident\n  span: 1:338-350\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 154\n  kind: RqOperator\n  span: 1:408-451\n  alias: d7\n  targets:\n  - 157\n  - 158\n  parent: 184\n- id: 157\n  kind: Literal\n  span: 1:421-451\n- id: 158\n  kind: Ident\n  span: 1:393-405\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 159\n  kind: RqOperator\n  span: 1:479-496\n  alias: d8\n  targets:\n  - 162\n  - 163\n  parent: 184\n- id: 162\n  kind: Literal\n  span: 1:492-496\n- id: 163\n  kind: Ident\n  span: 1:464-476\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 164\n  kind: RqOperator\n  span: 1:524-549\n  alias: d9\n  targets:\n  - 167\n  - 168\n  parent: 184\n- id: 167\n  kind: Literal\n  span: 1:537-549\n- id: 168\n  kind: Ident\n  span: 1:509-521\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 169\n  kind: RqOperator\n  span: 1:578-603\n  alias: d10\n  targets:\n  - 172\n  - 173\n  parent: 184\n- id: 172\n  kind: Literal\n  span: 1:591-603\n- id: 173\n  kind: Ident\n  span: 1:563-575\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 174\n  kind: RqOperator\n  span: 1:632-654\n  alias: d11\n  targets:\n  - 177\n  - 178\n  parent: 184\n- id: 177\n  kind: Literal\n  span: 1:645-654\n- id: 178\n  kind: Ident\n  span: 1:617-629\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 179\n  kind: RqOperator\n  span: 1:683-714\n  alias: d12\n  targets:\n  - 182\n  - 183\n  parent: 184\n- id: 182\n  kind: Literal\n  span: 1:696-714\n- id: 183\n  kind: Ident\n  span: 1:668-680\n  ident: !Ident\n  - this\n  - invoices\n  - invoice_date\n  targets:\n  - 119\n- id: 184\n  kind: Tuple\n  span: 1:86-718\n  children:\n  - 124\n  - 129\n  - 134\n  - 139\n  - 144\n  - 149\n  - 154\n  - 159\n  - 164\n  - 169\n  - 174\n  - 179\n  parent: 185\n- id: 185\n  kind: 'TransformCall: Select'\n  span: 1:79-718\n  children:\n  - 122\n  - 184\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:57-61\n              args:\n              - Ident:\n                - invoices\n                span: 1:62-70\n            span: 1:57-70\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:71-75\n              args:\n              - Literal:\n                  Integer: 20\n                span: 1:76-78\n            span: 1:71-78\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:79-85\n              args:\n              - Tuple:\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:98-110\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:113-125\n                        args:\n                        - Literal:\n                            String: '%Y/%m/%d'\n                          span: 1:126-136\n                      span: 1:113-136\n                  span: 1:97-137\n                  alias: d1\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:149-161\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:164-176\n                        args:\n                        - Literal:\n                            String: '%F'\n                          span: 1:177-181\n                      span: 1:164-181\n                  span: 1:148-182\n                  alias: d2\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:194-206\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:209-221\n                        args:\n                        - Literal:\n                            String: '%D'\n                          span: 1:222-226\n                      span: 1:209-226\n                  span: 1:193-227\n                  alias: d3\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:239-251\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:254-266\n                        args:\n                        - Literal:\n                            String: '%H:%M:%S.%f'\n                          span: 1:267-280\n                      span: 1:254-280\n                  span: 1:238-281\n                  alias: d4\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:293-305\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:308-320\n                        args:\n                        - Literal:\n                            String: '%r'\n                          span: 1:321-325\n                      span: 1:308-325\n                  span: 1:292-326\n                  alias: d5\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:338-350\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:353-365\n                        args:\n                        - Literal:\n                            String: '%A %B %-d %Y'\n                          span: 1:366-380\n                      span: 1:353-380\n                  span: 1:337-381\n                  alias: d6\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:393-405\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:408-420\n                        args:\n                        - Literal:\n                            String: '%a, %-d %b %Y at %I:%M:%S %p'\n                          span: 1:421-451\n                      span: 1:408-451\n                  span: 1:392-452\n                  alias: d7\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:464-476\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:479-491\n                        args:\n                        - Literal:\n                            String: '%+'\n                          span: 1:492-496\n                      span: 1:479-496\n                  span: 1:463-497\n                  alias: d8\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:509-521\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:524-536\n                        args:\n                        - Literal:\n                            String: '%-d/%-m/%y'\n                          span: 1:537-549\n                      span: 1:524-549\n                  span: 1:508-550\n                  alias: d9\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:563-575\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:578-590\n                        args:\n                        - Literal:\n                            String: '%-Hh %Mmin'\n                          span: 1:591-603\n                      span: 1:578-603\n                  span: 1:562-604\n                  alias: d10\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:617-629\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:632-644\n                        args:\n                        - Literal:\n                            String: '%M''%S\"'\n                          span: 1:645-654\n                      span: 1:632-654\n                  span: 1:616-655\n                  alias: d11\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - invoice_date\n                      span: 1:668-680\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - date\n                          - to_text\n                          span: 1:683-695\n                        args:\n                        - Literal:\n                            String: 100%% in %d days\n                          span: 1:696-714\n                      span: 1:683-714\n                  span: 1:667-715\n                  alias: d12\n                span: 1:86-718\n            span: 1:79-718\n        span: 1:57-718\n    span: 1:0-718\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__distinct.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {album_id, genre_id}\\ngroup tracks.* (take 1)\\nsort tracks.*\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct.prql\n---\nframes:\n- - 1:25-52\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - album_id\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 125\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:69-75\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - album_id\n      target_id: 129\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 130\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:77-90\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - album_id\n      target_id: 129\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 130\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 122\n  kind: Ident\n  span: 1:13-24\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 127\n- id: 124\n  kind: Ident\n  span: 1:33-41\n  ident: !Ident\n  - this\n  - tracks\n  - album_id\n  targets:\n  - 122\n  parent: 126\n- id: 125\n  kind: Ident\n  span: 1:43-51\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 122\n  parent: 126\n- id: 126\n  kind: Tuple\n  span: 1:32-52\n  children:\n  - 124\n  - 125\n  parent: 127\n- id: 127\n  kind: 'TransformCall: Select'\n  span: 1:25-52\n  children:\n  - 122\n  - 126\n  parent: 148\n- id: 129\n  kind: Ident\n  ident: !Ident\n  - this\n  - tracks\n  - album_id\n  targets:\n  - 124\n  parent: 131\n- id: 130\n  kind: Ident\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 125\n  parent: 131\n- id: 131\n  kind: Tuple\n  span: 1:59-67\n  children:\n  - 129\n  - 130\n- id: 148\n  kind: 'TransformCall: Take'\n  span: 1:69-75\n  children:\n  - 127\n  - 149\n  parent: 156\n- id: 149\n  kind: Literal\n  parent: 148\n- id: 153\n  kind: Ident\n  ident: !Ident\n  - this\n  - tracks\n  - album_id\n  targets:\n  - 129\n  parent: 156\n- id: 154\n  kind: Ident\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 130\n  parent: 156\n- id: 156\n  kind: 'TransformCall: Sort'\n  span: 1:77-90\n  children:\n  - 148\n  - 153\n  - 154\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:13-17\n              args:\n              - Ident:\n                - tracks\n                span: 1:18-24\n            span: 1:13-24\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:25-31\n              args:\n              - Tuple:\n                - Ident:\n                  - album_id\n                  span: 1:33-41\n                - Ident:\n                  - genre_id\n                  span: 1:43-51\n                span: 1:32-52\n            span: 1:25-52\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:53-58\n              args:\n              - Ident:\n                - tracks\n                - '*'\n                span: 1:59-67\n              - FuncCall:\n                  name:\n                    Ident:\n                    - take\n                    span: 1:69-73\n                  args:\n                  - Literal:\n                      Integer: 1\n                    span: 1:74-75\n                span: 1:69-75\n            span: 1:53-76\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:77-81\n              args:\n              - Ident:\n                - tracks\n                - '*'\n                span: 1:82-90\n            span: 1:77-90\n        span: 1:13-90\n    span: 1:0-90\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__distinct_on.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {genre_id, media_type_id, album_id}\\ngroup {genre_id, media_type_id} (sort {-album_id} | take 1)\\nsort {-genre_id, media_type_id}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct_on.prql\n---\nframes:\n- - 1:25-67\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - media_type_id\n      target_id: 125\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - album_id\n      target_id: 126\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:120-126\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 129\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - media_type_id\n      target_id: 130\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - album_id\n      target_id: 126\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:128-159\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 129\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - media_type_id\n      target_id: 130\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - album_id\n      target_id: 126\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 122\n  kind: Ident\n  span: 1:13-24\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 128\n- id: 124\n  kind: Ident\n  span: 1:33-41\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 122\n  parent: 127\n- id: 125\n  kind: Ident\n  span: 1:43-56\n  ident: !Ident\n  - this\n  - tracks\n  - media_type_id\n  targets:\n  - 122\n  parent: 127\n- id: 126\n  kind: Ident\n  span: 1:58-66\n  ident: !Ident\n  - this\n  - tracks\n  - album_id\n  targets:\n  - 122\n  parent: 127\n- id: 127\n  kind: Tuple\n  span: 1:32-67\n  children:\n  - 124\n  - 125\n  - 126\n  parent: 128\n- id: 128\n  kind: 'TransformCall: Select'\n  span: 1:25-67\n  children:\n  - 122\n  - 127\n  parent: 160\n- id: 129\n  kind: Ident\n  span: 1:75-83\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 124\n  parent: 131\n- id: 130\n  kind: Ident\n  span: 1:85-98\n  ident: !Ident\n  - this\n  - tracks\n  - media_type_id\n  targets:\n  - 125\n  parent: 131\n- id: 131\n  kind: Tuple\n  span: 1:74-99\n  children:\n  - 129\n  - 130\n- id: 156\n  kind: Ident\n  span: 1:108-116\n  ident: !Ident\n  - this\n  - tracks\n  - album_id\n  targets:\n  - 126\n- id: 160\n  kind: 'TransformCall: Take'\n  span: 1:120-126\n  children:\n  - 128\n  - 161\n  parent: 169\n- id: 161\n  kind: Literal\n  parent: 160\n- id: 166\n  kind: Ident\n  span: 1:135-143\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 129\n  parent: 169\n- id: 167\n  kind: Ident\n  span: 1:145-158\n  ident: !Ident\n  - this\n  - tracks\n  - media_type_id\n  targets:\n  - 130\n  parent: 169\n- id: 169\n  kind: 'TransformCall: Sort'\n  span: 1:128-159\n  children:\n  - 160\n  - 166\n  - 167\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:13-17\n              args:\n              - Ident:\n                - tracks\n                span: 1:18-24\n            span: 1:13-24\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:25-31\n              args:\n              - Tuple:\n                - Ident:\n                  - genre_id\n                  span: 1:33-41\n                - Ident:\n                  - media_type_id\n                  span: 1:43-56\n                - Ident:\n                  - album_id\n                  span: 1:58-66\n                span: 1:32-67\n            span: 1:25-67\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:68-73\n              args:\n              - Tuple:\n                - Ident:\n                  - genre_id\n                  span: 1:75-83\n                - Ident:\n                  - media_type_id\n                  span: 1:85-98\n                span: 1:74-99\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - sort\n                        span: 1:101-105\n                      args:\n                      - Tuple:\n                        - Unary:\n                            op: Neg\n                            expr:\n                              Ident:\n                              - album_id\n                              span: 1:108-116\n                          span: 1:107-116\n                        span: 1:106-117\n                    span: 1:101-117\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - take\n                        span: 1:120-124\n                      args:\n                      - Literal:\n                          Integer: 1\n                        span: 1:125-126\n                    span: 1:120-126\n                span: 1:101-126\n            span: 1:68-127\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:128-132\n              args:\n              - Tuple:\n                - Unary:\n                    op: Neg\n                    expr:\n                      Ident:\n                      - genre_id\n                      span: 1:135-143\n                  span: 1:134-143\n                - Ident:\n                  - media_type_id\n                  span: 1:145-158\n                span: 1:133-159\n            span: 1:128-159\n        span: 1:13-159\n    span: 1:0-159\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__genre_counts.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (ClickHouse prefers aliases to column names https://github.com/PRQL/prql/issues/2827)\\n# mssql:test\\nlet genre_count = (\\n    from genres\\n    aggregate {a = count name}\\n)\\n\\nfrom genre_count\\nfilter a > 0\\nselect a = -a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/genre_counts.prql\n---\nframes:\n- - 1:204-216\n  - columns:\n    - !Single\n      name:\n      - genre_count\n      - a\n      target_id: 134\n      target_name: a\n    inputs:\n    - id: 134\n      name: genre_count\n      table:\n      - default_db\n      - genres\n- - 1:217-230\n  - columns:\n    - !Single\n      name:\n      - a\n      target_id: 141\n      target_name: null\n    inputs:\n    - id: 134\n      name: genre_count\n      table:\n      - default_db\n      - genres\nnodes:\n- id: 134\n  kind: Ident\n  span: 1:187-203\n  ident: !Ident\n  - genre_count\n  parent: 140\n- id: 136\n  kind: RqOperator\n  span: 1:211-216\n  targets:\n  - 138\n  - 139\n  parent: 140\n- id: 138\n  kind: Ident\n  span: 1:211-212\n  ident: !Ident\n  - this\n  - genre_count\n  - a\n  targets:\n  - 134\n- id: 139\n  kind: Literal\n  span: 1:215-216\n- id: 140\n  kind: 'TransformCall: Filter'\n  span: 1:204-216\n  children:\n  - 134\n  - 136\n  parent: 145\n- id: 141\n  kind: RqOperator\n  span: 1:228-230\n  alias: a\n  targets:\n  - 143\n  parent: 144\n- id: 143\n  kind: Ident\n  span: 1:229-230\n  ident: !Ident\n  - this\n  - genre_count\n  - a\n  targets:\n  - 134\n- id: 144\n  kind: Tuple\n  span: 1:228-230\n  children:\n  - 141\n  parent: 145\n- id: 145\n  kind: 'TransformCall: Select'\n  span: 1:217-230\n  children:\n  - 140\n  - 144\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Let\n      name: genre_count\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:141-145\n              args:\n              - Ident:\n                - genres\n                span: 1:146-152\n            span: 1:141-152\n          - FuncCall:\n              name:\n                Ident:\n                - aggregate\n                span: 1:157-166\n              args:\n              - Tuple:\n                - FuncCall:\n                    name:\n                      Ident:\n                      - count\n                      span: 1:172-177\n                    args:\n                    - Ident:\n                      - name\n                      span: 1:178-182\n                  span: 1:172-182\n                  alias: a\n                span: 1:167-183\n            span: 1:157-183\n        span: 1:135-185\n    span: 1:0-185\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:187-191\n              args:\n              - Ident:\n                - genre_count\n                span: 1:192-203\n            span: 1:187-203\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:204-210\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - a\n                    span: 1:211-212\n                  op: Gt\n                  right:\n                    Literal:\n                      Integer: 0\n                    span: 1:215-216\n                span: 1:211-216\n            span: 1:204-216\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:217-223\n              args:\n              - Unary:\n                  op: Neg\n                  expr:\n                    Ident:\n                    - a\n                    span: 1:229-230\n                span: 1:228-230\n                alias: a\n            span: 1:217-230\n        span: 1:187-230\n    span: 1:185-230\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__group_all.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom a=albums\\ntake 10\\njoin tracks (==album_id)\\ngroup {a.album_id, a.title} (aggregate price = (sum tracks.unit_price | math.round 2))\\nsort album_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_all.prql\n---\nframes:\n- - 1:27-34\n  - columns:\n    - !All\n      input_id: 126\n      except: []\n    inputs:\n    - id: 126\n      name: a\n      table:\n      - default_db\n      - albums\n- - 1:35-59\n  - columns:\n    - !All\n      input_id: 126\n      except: []\n    - !All\n      input_id: 120\n      except: []\n    inputs:\n    - id: 126\n      name: a\n      table:\n      - default_db\n      - albums\n    - id: 120\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:89-145\n  - columns:\n    - !Single\n      name:\n      - a\n      - album_id\n      target_id: 136\n      target_name: null\n    - !Single\n      name:\n      - a\n      - title\n      target_id: 137\n      target_name: null\n    - !Single\n      name:\n      - price\n      target_id: 155\n      target_name: null\n    inputs:\n    - id: 126\n      name: a\n      table:\n      - default_db\n      - albums\n    - id: 120\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:147-160\n  - columns:\n    - !Single\n      name:\n      - a\n      - album_id\n      target_id: 136\n      target_name: null\n    - !Single\n      name:\n      - a\n      - title\n      target_id: 137\n      target_name: null\n    - !Single\n      name:\n      - price\n      target_id: 155\n      target_name: null\n    inputs:\n    - id: 126\n      name: a\n      table:\n      - default_db\n      - albums\n    - id: 120\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 120\n  kind: Ident\n  span: 1:40-46\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 135\n- id: 126\n  kind: Ident\n  span: 1:13-26\n  ident: !Ident\n  - default_db\n  - albums\n  parent: 129\n- id: 129\n  kind: 'TransformCall: Take'\n  span: 1:27-34\n  children:\n  - 126\n  - 130\n  parent: 135\n- id: 130\n  kind: Literal\n  parent: 129\n- id: 131\n  kind: RqOperator\n  span: 1:48-58\n  targets:\n  - 133\n  - 134\n  parent: 135\n- id: 133\n  kind: Ident\n  span: 1:50-58\n  ident: !Ident\n  - this\n  - a\n  - album_id\n  targets:\n  - 126\n- id: 134\n  kind: Ident\n  span: 1:50-58\n  ident: !Ident\n  - that\n  - tracks\n  - album_id\n  targets:\n  - 120\n- id: 135\n  kind: 'TransformCall: Join'\n  span: 1:35-59\n  children:\n  - 129\n  - 120\n  - 131\n  parent: 163\n- id: 136\n  kind: Ident\n  span: 1:67-77\n  ident: !Ident\n  - this\n  - a\n  - album_id\n  targets:\n  - 126\n  parent: 138\n- id: 137\n  kind: Ident\n  span: 1:79-86\n  ident: !Ident\n  - this\n  - a\n  - title\n  targets:\n  - 126\n  parent: 138\n- id: 138\n  kind: Tuple\n  span: 1:66-87\n  children:\n  - 136\n  - 137\n  parent: 163\n- id: 155\n  kind: RqOperator\n  span: 1:132-144\n  alias: price\n  targets:\n  - 158\n  - 159\n  parent: 162\n- id: 158\n  kind: Literal\n  span: 1:143-144\n- id: 159\n  kind: RqOperator\n  span: 1:108-129\n  targets:\n  - 161\n- id: 161\n  kind: Ident\n  span: 1:112-129\n  ident: !Ident\n  - this\n  - tracks\n  - unit_price\n  targets:\n  - 120\n- id: 162\n  kind: Tuple\n  span: 1:132-144\n  children:\n  - 155\n  parent: 163\n- id: 163\n  kind: 'TransformCall: Aggregate'\n  span: 1:89-145\n  children:\n  - 135\n  - 162\n  - 138\n  parent: 168\n- id: 166\n  kind: Ident\n  span: 1:152-160\n  ident: !Ident\n  - this\n  - a\n  - album_id\n  targets:\n  - 136\n  parent: 168\n- id: 168\n  kind: 'TransformCall: Sort'\n  span: 1:147-160\n  children:\n  - 163\n  - 166\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:13-17\n              args:\n              - Ident:\n                - albums\n                span: 1:20-26\n                alias: a\n            span: 1:13-26\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:27-31\n              args:\n              - Literal:\n                  Integer: 10\n                span: 1:32-34\n            span: 1:27-34\n          - FuncCall:\n              name:\n                Ident:\n                - join\n                span: 1:35-39\n              args:\n              - Ident:\n                - tracks\n                span: 1:40-46\n              - Unary:\n                  op: EqSelf\n                  expr:\n                    Ident:\n                    - album_id\n                    span: 1:50-58\n                span: 1:48-58\n            span: 1:35-59\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:60-65\n              args:\n              - Tuple:\n                - Ident:\n                  - a\n                  - album_id\n                  span: 1:67-77\n                - Ident:\n                  - a\n                  - title\n                  span: 1:79-86\n                span: 1:66-87\n              - FuncCall:\n                  name:\n                    Ident:\n                    - aggregate\n                    span: 1:89-98\n                  args:\n                  - Pipeline:\n                      exprs:\n                      - FuncCall:\n                          name:\n                            Ident:\n                            - sum\n                            span: 1:108-111\n                          args:\n                          - Ident:\n                            - tracks\n                            - unit_price\n                            span: 1:112-129\n                        span: 1:108-129\n                      - FuncCall:\n                          name:\n                            Ident:\n                            - math\n                            - round\n                            span: 1:132-142\n                          args:\n                          - Literal:\n                              Integer: 2\n                            span: 1:143-144\n                        span: 1:132-144\n                    span: 1:108-144\n                    alias: price\n                span: 1:89-145\n            span: 1:60-146\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:147-151\n              args:\n              - Ident:\n                - album_id\n                span: 1:152-160\n            span: 1:147-160\n        span: 1:13-160\n    span: 1:0-160\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__group_sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nderive d = album_id + 1\\ngroup d (\\n    aggregate {\\n        n1 = (track_id | sum),\\n    }\\n)\\nsort d\\ntake 10\\nselect { d1 = d, n1 }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort.prql\n---\nframes:\n- - 1:25-48\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    - !Single\n      name:\n      - d\n      target_id: 130\n      target_name: null\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:63-111\n  - columns:\n    - !Single\n      name:\n      - d\n      target_id: 136\n      target_name: null\n    - !Single\n      name:\n      - n1\n      target_id: 153\n      target_name: null\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:114-120\n  - columns:\n    - !Single\n      name:\n      - d\n      target_id: 136\n      target_name: null\n    - !Single\n      name:\n      - n1\n      target_id: 153\n      target_name: null\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:121-128\n  - columns:\n    - !Single\n      name:\n      - d\n      target_id: 136\n      target_name: null\n    - !Single\n      name:\n      - n1\n      target_id: 153\n      target_name: null\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:129-150\n  - columns:\n    - !Single\n      name:\n      - d1\n      target_id: 166\n      target_name: null\n    - !Single\n      name:\n      - n1\n      target_id: 167\n      target_name: null\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 128\n  kind: Ident\n  span: 1:13-24\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 135\n- id: 130\n  kind: RqOperator\n  span: 1:36-48\n  alias: d\n  targets:\n  - 132\n  - 133\n  parent: 134\n- id: 132\n  kind: Ident\n  span: 1:36-44\n  ident: !Ident\n  - this\n  - tracks\n  - album_id\n  targets:\n  - 128\n- id: 133\n  kind: Literal\n  span: 1:47-48\n- id: 134\n  kind: Tuple\n  span: 1:36-48\n  children:\n  - 130\n  parent: 135\n- id: 135\n  kind: 'TransformCall: Derive'\n  span: 1:25-48\n  children:\n  - 128\n  - 134\n  parent: 157\n- id: 136\n  kind: Ident\n  span: 1:55-56\n  ident: !Ident\n  - this\n  - d\n  targets:\n  - 130\n  parent: 139\n- id: 139\n  kind: Tuple\n  span: 1:55-56\n  children:\n  - 136\n  parent: 157\n- id: 153\n  kind: RqOperator\n  span: 1:100-103\n  alias: n1\n  targets:\n  - 155\n  parent: 156\n- id: 155\n  kind: Ident\n  span: 1:89-97\n  ident: !Ident\n  - this\n  - tracks\n  - track_id\n  targets:\n  - 128\n- id: 156\n  kind: Tuple\n  span: 1:73-111\n  children:\n  - 153\n  parent: 157\n- id: 157\n  kind: 'TransformCall: Aggregate'\n  span: 1:63-111\n  children:\n  - 135\n  - 156\n  - 139\n  parent: 162\n- id: 160\n  kind: Ident\n  span: 1:119-120\n  ident: !Ident\n  - this\n  - d\n  targets:\n  - 136\n  parent: 162\n- id: 162\n  kind: 'TransformCall: Sort'\n  span: 1:114-120\n  children:\n  - 157\n  - 160\n  parent: 164\n- id: 164\n  kind: 'TransformCall: Take'\n  span: 1:121-128\n  children:\n  - 162\n  - 165\n  parent: 169\n- id: 165\n  kind: Literal\n  parent: 164\n- id: 166\n  kind: Ident\n  span: 1:143-144\n  alias: d1\n  ident: !Ident\n  - this\n  - d\n  targets:\n  - 136\n  parent: 168\n- id: 167\n  kind: Ident\n  span: 1:146-148\n  ident: !Ident\n  - this\n  - n1\n  targets:\n  - 153\n  parent: 168\n- id: 168\n  kind: Tuple\n  span: 1:136-150\n  children:\n  - 166\n  - 167\n  parent: 169\n- id: 169\n  kind: 'TransformCall: Select'\n  span: 1:129-150\n  children:\n  - 164\n  - 168\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:13-17\n              args:\n              - Ident:\n                - tracks\n                span: 1:18-24\n            span: 1:13-24\n          - FuncCall:\n              name:\n                Ident:\n                - derive\n                span: 1:25-31\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - album_id\n                    span: 1:36-44\n                  op: Add\n                  right:\n                    Literal:\n                      Integer: 1\n                    span: 1:47-48\n                span: 1:36-48\n                alias: d\n            span: 1:25-48\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:49-54\n              args:\n              - Ident:\n                - d\n                span: 1:55-56\n              - FuncCall:\n                  name:\n                    Ident:\n                    - aggregate\n                    span: 1:63-72\n                  args:\n                  - Tuple:\n                    - Pipeline:\n                        exprs:\n                        - Ident:\n                          - track_id\n                          span: 1:89-97\n                        - Ident:\n                          - sum\n                          span: 1:100-103\n                      span: 1:88-104\n                      alias: n1\n                    span: 1:73-111\n                span: 1:63-111\n            span: 1:49-113\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:114-118\n              args:\n              - Ident:\n                - d\n                span: 1:119-120\n            span: 1:114-120\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:121-125\n              args:\n              - Literal:\n                  Integer: 10\n                span: 1:126-128\n            span: 1:121-128\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:129-135\n              args:\n              - Tuple:\n                - Ident:\n                  - d\n                  span: 1:143-144\n                  alias: d1\n                - Ident:\n                  - n1\n                  span: 1:146-148\n                span: 1:136-150\n            span: 1:129-150\n        span: 1:13-150\n    span: 1:0-150\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__group_sort_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_derive_select_join.prql\n---\nframes:\n- - 1:66-117\n  - columns:\n    - !Single\n      name:\n      - _literal_127\n      - artist_id\n      target_id: 128\n      target_name: null\n    - !Single\n      name:\n      - album_title_count\n      target_id: 147\n      target_name: null\n    inputs:\n    - id: 127\n      name: _literal_127\n      table:\n      - default_db\n      - _literal_127\n- - 1:119-164\n  - columns:\n    - !Single\n      name:\n      - _literal_127\n      - artist_id\n      target_id: 128\n      target_name: null\n    - !Single\n      name:\n      - album_title_count\n      target_id: 147\n      target_name: null\n    inputs:\n    - id: 127\n      name: _literal_127\n      table:\n      - default_db\n      - _literal_127\n- - 1:165-214\n  - columns:\n    - !Single\n      name:\n      - _literal_127\n      - artist_id\n      target_id: 128\n      target_name: null\n    - !Single\n      name:\n      - album_title_count\n      target_id: 147\n      target_name: null\n    - !Single\n      name:\n      - new_album_count\n      target_id: 157\n      target_name: null\n    inputs:\n    - id: 127\n      name: _literal_127\n      table:\n      - default_db\n      - _literal_127\n- - 1:215-260\n  - columns:\n    - !Single\n      name:\n      - _literal_127\n      - artist_id\n      target_id: 160\n      target_name: null\n    - !Single\n      name:\n      - new_album_count\n      target_id: 161\n      target_name: null\n    inputs:\n    - id: 127\n      name: _literal_127\n      table:\n      - default_db\n      - _literal_127\n- - 1:261-367\n  - columns:\n    - !Single\n      name:\n      - _literal_127\n      - artist_id\n      target_id: 160\n      target_name: null\n    - !Single\n      name:\n      - new_album_count\n      target_id: 161\n      target_name: null\n    - !All\n      input_id: 114\n      except: []\n    inputs:\n    - id: 127\n      name: _literal_127\n      table:\n      - default_db\n      - _literal_127\n    - id: 114\n      name: _literal_114\n      table:\n      - default_db\n      - _literal_114\nnodes:\n- id: 114\n  kind: SString\n  span: 1:278-330\n  parent: 168\n- id: 127\n  kind: SString\n  span: 1:0-46\n  parent: 150\n- id: 128\n  kind: Ident\n  span: 1:54-63\n  ident: !Ident\n  - this\n  - _literal_127\n  - artist_id\n  targets:\n  - 127\n  parent: 129\n- id: 129\n  kind: Tuple\n  span: 1:53-64\n  children:\n  - 128\n  parent: 150\n- id: 147\n  kind: RqOperator\n  span: 1:98-116\n  alias: album_title_count\n  targets:\n  - 148\n  parent: 149\n- id: 148\n  kind: Literal\n- id: 149\n  kind: Tuple\n  span: 1:76-117\n  children:\n  - 147\n  parent: 150\n- id: 150\n  kind: 'TransformCall: Aggregate'\n  span: 1:66-117\n  children:\n  - 127\n  - 149\n  - 129\n  parent: 156\n- id: 153\n  kind: Ident\n  span: 1:125-139\n  ident: !Ident\n  - this\n  - _literal_127\n  - artist_id\n  targets:\n  - 128\n  parent: 156\n- id: 154\n  kind: Ident\n  span: 1:141-163\n  ident: !Ident\n  - this\n  - album_title_count\n  targets:\n  - 147\n  parent: 156\n- id: 156\n  kind: 'TransformCall: Sort'\n  span: 1:119-164\n  children:\n  - 150\n  - 153\n  - 154\n  parent: 159\n- id: 157\n  kind: Ident\n  span: 1:191-213\n  alias: new_album_count\n  ident: !Ident\n  - this\n  - album_title_count\n  targets:\n  - 147\n  parent: 158\n- id: 158\n  kind: Tuple\n  span: 1:172-214\n  children:\n  - 157\n  parent: 159\n- id: 159\n  kind: 'TransformCall: Derive'\n  span: 1:165-214\n  children:\n  - 156\n  - 158\n  parent: 163\n- id: 160\n  kind: Ident\n  span: 1:223-237\n  ident: !Ident\n  - this\n  - _literal_127\n  - artist_id\n  targets:\n  - 128\n  parent: 162\n- id: 161\n  kind: Ident\n  span: 1:239-259\n  ident: !Ident\n  - this\n  - new_album_count\n  targets:\n  - 157\n  parent: 162\n- id: 162\n  kind: Tuple\n  span: 1:222-260\n  children:\n  - 160\n  - 161\n  parent: 163\n- id: 163\n  kind: 'TransformCall: Select'\n  span: 1:215-260\n  children:\n  - 159\n  - 162\n  parent: 168\n- id: 164\n  kind: RqOperator\n  span: 1:334-366\n  targets:\n  - 166\n  - 167\n  parent: 168\n- id: 166\n  kind: Ident\n  span: 1:334-348\n  ident: !Ident\n  - this\n  - _literal_127\n  - artist_id\n  targets:\n  - 160\n- id: 167\n  kind: Ident\n  span: 1:352-366\n  ident: !Ident\n  - that\n  - _literal_114\n  - artist_id\n  targets:\n  - 114\n- id: 168\n  kind: 'TransformCall: Join'\n  span: 1:261-367\n  children:\n  - 163\n  - 114\n  - 164\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - SString:\n            - !String SELECT album_id,title,artist_id FROM albums\n            span: 1:0-46\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:47-52\n              args:\n              - Tuple:\n                - Ident:\n                  - artist_id\n                  span: 1:54-63\n                span: 1:53-64\n              - FuncCall:\n                  name:\n                    Ident:\n                    - aggregate\n                    span: 1:66-75\n                  args:\n                  - Tuple:\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - count\n                          span: 1:98-103\n                        args:\n                        - Ident:\n                          - this\n                          - title\n                          span: 1:104-116\n                      span: 1:98-116\n                      alias: album_title_count\n                    span: 1:76-117\n                span: 1:66-117\n            span: 1:47-118\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:119-123\n              args:\n              - Tuple:\n                - Ident:\n                  - this\n                  - artist_id\n                  span: 1:125-139\n                - Ident:\n                  - this\n                  - album_title_count\n                  span: 1:141-163\n                span: 1:124-164\n            span: 1:119-164\n          - FuncCall:\n              name:\n                Ident:\n                - derive\n                span: 1:165-171\n              args:\n              - Tuple:\n                - Ident:\n                  - this\n                  - album_title_count\n                  span: 1:191-213\n                  alias: new_album_count\n                span: 1:172-214\n            span: 1:165-214\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:215-221\n              args:\n              - Tuple:\n                - Ident:\n                  - this\n                  - artist_id\n                  span: 1:223-237\n                - Ident:\n                  - this\n                  - new_album_count\n                  span: 1:239-259\n                span: 1:222-260\n            span: 1:215-260\n          - FuncCall:\n              name:\n                Ident:\n                - join\n                span: 1:261-265\n              args:\n              - SString:\n                - !String SELECT artist_id,name as artist_name FROM artists\n                span: 1:278-330\n              - Binary:\n                  left:\n                    Ident:\n                    - this\n                    - artist_id\n                    span: 1:334-348\n                  op: Eq\n                  right:\n                    Ident:\n                    - that\n                    - artist_id\n                    span: 1:352-366\n                span: 1:334-366\n              named_args:\n                side:\n                  Ident:\n                  - left\n                  span: 1:271-275\n            span: 1:261-367\n        span: 1:0-367\n    span: 1:0-367\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__group_sort_filter_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nfilter (this.album_title_count) > 10\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_filter_derive_select_join.prql\n---\nframes:\n- - 1:66-117\n  - columns:\n    - !Single\n      name:\n      - _literal_130\n      - artist_id\n      target_id: 131\n      target_name: null\n    - !Single\n      name:\n      - album_title_count\n      target_id: 150\n      target_name: null\n    inputs:\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n- - 1:119-164\n  - columns:\n    - !Single\n      name:\n      - _literal_130\n      - artist_id\n      target_id: 131\n      target_name: null\n    - !Single\n      name:\n      - album_title_count\n      target_id: 150\n      target_name: null\n    inputs:\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n- - 1:165-201\n  - columns:\n    - !Single\n      name:\n      - _literal_130\n      - artist_id\n      target_id: 131\n      target_name: null\n    - !Single\n      name:\n      - album_title_count\n      target_id: 150\n      target_name: null\n    inputs:\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n- - 1:202-251\n  - columns:\n    - !Single\n      name:\n      - _literal_130\n      - artist_id\n      target_id: 131\n      target_name: null\n    - !Single\n      name:\n      - album_title_count\n      target_id: 150\n      target_name: null\n    - !Single\n      name:\n      - new_album_count\n      target_id: 165\n      target_name: null\n    inputs:\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n- - 1:252-297\n  - columns:\n    - !Single\n      name:\n      - _literal_130\n      - artist_id\n      target_id: 168\n      target_name: null\n    - !Single\n      name:\n      - new_album_count\n      target_id: 169\n      target_name: null\n    inputs:\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n- - 1:298-404\n  - columns:\n    - !Single\n      name:\n      - _literal_130\n      - artist_id\n      target_id: 168\n      target_name: null\n    - !Single\n      name:\n      - new_album_count\n      target_id: 169\n      target_name: null\n    - !All\n      input_id: 114\n      except: []\n    inputs:\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n    - id: 114\n      name: _literal_114\n      table:\n      - default_db\n      - _literal_114\nnodes:\n- id: 114\n  kind: SString\n  span: 1:315-367\n  parent: 176\n- id: 130\n  kind: SString\n  span: 1:0-46\n  parent: 153\n- id: 131\n  kind: Ident\n  span: 1:54-63\n  ident: !Ident\n  - this\n  - _literal_130\n  - artist_id\n  targets:\n  - 130\n  parent: 132\n- id: 132\n  kind: Tuple\n  span: 1:53-64\n  children:\n  - 131\n  parent: 153\n- id: 150\n  kind: RqOperator\n  span: 1:98-116\n  alias: album_title_count\n  targets:\n  - 151\n  parent: 152\n- id: 151\n  kind: Literal\n- id: 152\n  kind: Tuple\n  span: 1:76-117\n  children:\n  - 150\n  parent: 153\n- id: 153\n  kind: 'TransformCall: Aggregate'\n  span: 1:66-117\n  children:\n  - 130\n  - 152\n  - 132\n  parent: 159\n- id: 156\n  kind: Ident\n  span: 1:125-139\n  ident: !Ident\n  - this\n  - _literal_130\n  - artist_id\n  targets:\n  - 131\n  parent: 159\n- id: 157\n  kind: Ident\n  span: 1:141-163\n  ident: !Ident\n  - this\n  - album_title_count\n  targets:\n  - 150\n  parent: 159\n- id: 159\n  kind: 'TransformCall: Sort'\n  span: 1:119-164\n  children:\n  - 153\n  - 156\n  - 157\n  parent: 164\n- id: 160\n  kind: RqOperator\n  span: 1:172-201\n  targets:\n  - 162\n  - 163\n  parent: 164\n- id: 162\n  kind: Ident\n  span: 1:173-195\n  ident: !Ident\n  - this\n  - album_title_count\n  targets:\n  - 150\n- id: 163\n  kind: Literal\n  span: 1:199-201\n- id: 164\n  kind: 'TransformCall: Filter'\n  span: 1:165-201\n  children:\n  - 159\n  - 160\n  parent: 167\n- id: 165\n  kind: Ident\n  span: 1:228-250\n  alias: new_album_count\n  ident: !Ident\n  - this\n  - album_title_count\n  targets:\n  - 150\n  parent: 166\n- id: 166\n  kind: Tuple\n  span: 1:209-251\n  children:\n  - 165\n  parent: 167\n- id: 167\n  kind: 'TransformCall: Derive'\n  span: 1:202-251\n  children:\n  - 164\n  - 166\n  parent: 171\n- id: 168\n  kind: Ident\n  span: 1:260-274\n  ident: !Ident\n  - this\n  - _literal_130\n  - artist_id\n  targets:\n  - 131\n  parent: 170\n- id: 169\n  kind: Ident\n  span: 1:276-296\n  ident: !Ident\n  - this\n  - new_album_count\n  targets:\n  - 165\n  parent: 170\n- id: 170\n  kind: Tuple\n  span: 1:259-297\n  children:\n  - 168\n  - 169\n  parent: 171\n- id: 171\n  kind: 'TransformCall: Select'\n  span: 1:252-297\n  children:\n  - 167\n  - 170\n  parent: 176\n- id: 172\n  kind: RqOperator\n  span: 1:371-403\n  targets:\n  - 174\n  - 175\n  parent: 176\n- id: 174\n  kind: Ident\n  span: 1:371-385\n  ident: !Ident\n  - this\n  - _literal_130\n  - artist_id\n  targets:\n  - 168\n- id: 175\n  kind: Ident\n  span: 1:389-403\n  ident: !Ident\n  - that\n  - _literal_114\n  - artist_id\n  targets:\n  - 114\n- id: 176\n  kind: 'TransformCall: Join'\n  span: 1:298-404\n  children:\n  - 171\n  - 114\n  - 172\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - SString:\n            - !String SELECT album_id,title,artist_id FROM albums\n            span: 1:0-46\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:47-52\n              args:\n              - Tuple:\n                - Ident:\n                  - artist_id\n                  span: 1:54-63\n                span: 1:53-64\n              - FuncCall:\n                  name:\n                    Ident:\n                    - aggregate\n                    span: 1:66-75\n                  args:\n                  - Tuple:\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - count\n                          span: 1:98-103\n                        args:\n                        - Ident:\n                          - this\n                          - title\n                          span: 1:104-116\n                      span: 1:98-116\n                      alias: album_title_count\n                    span: 1:76-117\n                span: 1:66-117\n            span: 1:47-118\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:119-123\n              args:\n              - Tuple:\n                - Ident:\n                  - this\n                  - artist_id\n                  span: 1:125-139\n                - Ident:\n                  - this\n                  - album_title_count\n                  span: 1:141-163\n                span: 1:124-164\n            span: 1:119-164\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:165-171\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - this\n                    - album_title_count\n                    span: 1:173-195\n                  op: Gt\n                  right:\n                    Literal:\n                      Integer: 10\n                    span: 1:199-201\n                span: 1:172-201\n            span: 1:165-201\n          - FuncCall:\n              name:\n                Ident:\n                - derive\n                span: 1:202-208\n              args:\n              - Tuple:\n                - Ident:\n                  - this\n                  - album_title_count\n                  span: 1:228-250\n                  alias: new_album_count\n                span: 1:209-251\n            span: 1:202-251\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:252-258\n              args:\n              - Tuple:\n                - Ident:\n                  - this\n                  - artist_id\n                  span: 1:260-274\n                - Ident:\n                  - this\n                  - new_album_count\n                  span: 1:276-296\n                span: 1:259-297\n            span: 1:252-297\n          - FuncCall:\n              name:\n                Ident:\n                - join\n                span: 1:298-302\n              args:\n              - SString:\n                - !String SELECT artist_id,name as artist_name FROM artists\n                span: 1:315-367\n              - Binary:\n                  left:\n                    Ident:\n                    - this\n                    - artist_id\n                    span: 1:371-385\n                  op: Eq\n                  right:\n                    Ident:\n                    - that\n                    - artist_id\n                    span: 1:389-403\n                span: 1:371-403\n              named_args:\n                side:\n                  Ident:\n                  - left\n                  span: 1:308-312\n            span: 1:298-404\n        span: 1:0-404\n    span: 1:0-404\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__group_sort_limit_take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# Compute the 3 longest songs for each genre and sort by genre\\n# mssql:test\\nfrom tracks\\nselect {genre_id,milliseconds}\\ngroup {genre_id} (\\n  sort {-milliseconds}\\n  take 3\\n)\\njoin genres (==genre_id)\\nselect {name, milliseconds}\\nsort {+name,-milliseconds}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_limit_take.prql\n---\nframes:\n- - 1:88-118\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 131\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - milliseconds\n      target_id: 132\n      target_name: null\n    inputs:\n    - id: 129\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:163-169\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 135\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - milliseconds\n      target_id: 132\n      target_name: null\n    inputs:\n    - id: 129\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:172-196\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 135\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - milliseconds\n      target_id: 132\n      target_name: null\n    - !All\n      input_id: 120\n      except: []\n    inputs:\n    - id: 129\n      name: tracks\n      table:\n      - default_db\n      - tracks\n    - id: 120\n      name: genres\n      table:\n      - default_db\n      - genres\n- - 1:197-224\n  - columns:\n    - !Single\n      name:\n      - genres\n      - name\n      target_id: 173\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - milliseconds\n      target_id: 174\n      target_name: null\n    inputs:\n    - id: 129\n      name: tracks\n      table:\n      - default_db\n      - tracks\n    - id: 120\n      name: genres\n      table:\n      - default_db\n      - genres\n- - 1:225-251\n  - columns:\n    - !Single\n      name:\n      - genres\n      - name\n      target_id: 173\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - milliseconds\n      target_id: 174\n      target_name: null\n    inputs:\n    - id: 129\n      name: tracks\n      table:\n      - default_db\n      - tracks\n    - id: 120\n      name: genres\n      table:\n      - default_db\n      - genres\nnodes:\n- id: 120\n  kind: Ident\n  span: 1:177-183\n  ident: !Ident\n  - default_db\n  - genres\n  parent: 172\n- id: 129\n  kind: Ident\n  span: 1:76-87\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 134\n- id: 131\n  kind: Ident\n  span: 1:96-104\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 129\n  parent: 133\n- id: 132\n  kind: Ident\n  span: 1:105-117\n  ident: !Ident\n  - this\n  - tracks\n  - milliseconds\n  targets:\n  - 129\n  parent: 133\n- id: 133\n  kind: Tuple\n  span: 1:95-118\n  children:\n  - 131\n  - 132\n  parent: 134\n- id: 134\n  kind: 'TransformCall: Select'\n  span: 1:88-118\n  children:\n  - 129\n  - 133\n  parent: 164\n- id: 135\n  kind: Ident\n  span: 1:126-134\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 131\n  parent: 136\n- id: 136\n  kind: Tuple\n  span: 1:125-135\n  children:\n  - 135\n- id: 160\n  kind: Ident\n  span: 1:147-159\n  ident: !Ident\n  - this\n  - tracks\n  - milliseconds\n  targets:\n  - 132\n- id: 164\n  kind: 'TransformCall: Take'\n  span: 1:163-169\n  children:\n  - 134\n  - 165\n  parent: 172\n- id: 165\n  kind: Literal\n  parent: 164\n- id: 168\n  kind: RqOperator\n  span: 1:185-195\n  targets:\n  - 170\n  - 171\n  parent: 172\n- id: 170\n  kind: Ident\n  span: 1:187-195\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 135\n- id: 171\n  kind: Ident\n  span: 1:187-195\n  ident: !Ident\n  - that\n  - genres\n  - genre_id\n  targets:\n  - 120\n- id: 172\n  kind: 'TransformCall: Join'\n  span: 1:172-196\n  children:\n  - 164\n  - 120\n  - 168\n  parent: 176\n- id: 173\n  kind: Ident\n  span: 1:205-209\n  ident: !Ident\n  - this\n  - genres\n  - name\n  targets:\n  - 120\n  parent: 175\n- id: 174\n  kind: Ident\n  span: 1:211-223\n  ident: !Ident\n  - this\n  - tracks\n  - milliseconds\n  targets:\n  - 132\n  parent: 175\n- id: 175\n  kind: Tuple\n  span: 1:204-224\n  children:\n  - 173\n  - 174\n  parent: 176\n- id: 176\n  kind: 'TransformCall: Select'\n  span: 1:197-224\n  children:\n  - 172\n  - 175\n  parent: 182\n- id: 177\n  kind: Ident\n  span: 1:231-236\n  ident: !Ident\n  - this\n  - genres\n  - name\n  targets:\n  - 173\n  parent: 182\n- id: 180\n  kind: Ident\n  span: 1:238-250\n  ident: !Ident\n  - this\n  - tracks\n  - milliseconds\n  targets:\n  - 174\n  parent: 182\n- id: 182\n  kind: 'TransformCall: Sort'\n  span: 1:225-251\n  children:\n  - 176\n  - 177\n  - 180\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:76-80\n              args:\n              - Ident:\n                - tracks\n                span: 1:81-87\n            span: 1:76-87\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:88-94\n              args:\n              - Tuple:\n                - Ident:\n                  - genre_id\n                  span: 1:96-104\n                - Ident:\n                  - milliseconds\n                  span: 1:105-117\n                span: 1:95-118\n            span: 1:88-118\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:119-124\n              args:\n              - Tuple:\n                - Ident:\n                  - genre_id\n                  span: 1:126-134\n                span: 1:125-135\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - sort\n                        span: 1:140-144\n                      args:\n                      - Tuple:\n                        - Unary:\n                            op: Neg\n                            expr:\n                              Ident:\n                              - milliseconds\n                              span: 1:147-159\n                          span: 1:146-159\n                        span: 1:145-160\n                    span: 1:140-160\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - take\n                        span: 1:163-167\n                      args:\n                      - Literal:\n                          Integer: 3\n                        span: 1:168-169\n                    span: 1:163-169\n                span: 1:140-169\n            span: 1:119-171\n          - FuncCall:\n              name:\n                Ident:\n                - join\n                span: 1:172-176\n              args:\n              - Ident:\n                - genres\n                span: 1:177-183\n              - Unary:\n                  op: EqSelf\n                  expr:\n                    Ident:\n                    - genre_id\n                    span: 1:187-195\n                span: 1:185-195\n            span: 1:172-196\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:197-203\n              args:\n              - Tuple:\n                - Ident:\n                  - name\n                  span: 1:205-209\n                - Ident:\n                  - milliseconds\n                  span: 1:211-223\n                span: 1:204-224\n            span: 1:197-224\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:225-229\n              args:\n              - Tuple:\n                - Unary:\n                    op: Add\n                    expr:\n                      Ident:\n                      - name\n                      span: 1:232-236\n                  span: 1:231-236\n                - Unary:\n                    op: Neg\n                    expr:\n                      Ident:\n                      - milliseconds\n                      span: 1:238-250\n                  span: 1:237-250\n                span: 1:230-251\n            span: 1:225-251\n        span: 1:76-251\n    span: 1:0-251\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__invoice_totals.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (clickhouse doesn't have lag function)\\n\\n#! Calculate a number of metrics about the sales of tracks in each city.\\nfrom i=invoices\\njoin ii=invoice_items (==invoice_id)\\nderive {\\n    city = i.billing_city,\\n    street = i.billing_address,\\n}\\ngroup {city, street} (\\n    derive total = ii.unit_price * ii.quantity\\n    aggregate {\\n        num_orders = count_distinct i.invoice_id,\\n        num_tracks = sum ii.quantity,\\n        total_price = sum total,\\n    }\\n)\\ngroup {city} (\\n    sort street\\n    window expanding:true (\\n        derive {running_total_num_tracks = sum num_tracks}\\n    )\\n)\\nsort {city, street}\\nderive {num_tracks_last_week = lag 7 num_tracks}\\nselect {\\n    city,\\n    street,\\n    num_orders,\\n    num_tracks,\\n    running_total_num_tracks,\\n    num_tracks_last_week\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/invoice_totals.prql\n---\nframes:\n- - 1:147-183\n  - columns:\n    - !All\n      input_id: 138\n      except: []\n    - !All\n      input_id: 135\n      except: []\n    inputs:\n    - id: 138\n      name: i\n      table:\n      - default_db\n      - invoices\n    - id: 135\n      name: ii\n      table:\n      - default_db\n      - invoice_items\n- - 1:184-253\n  - columns:\n    - !All\n      input_id: 138\n      except: []\n    - !All\n      input_id: 135\n      except: []\n    - !Single\n      name:\n      - city\n      target_id: 145\n      target_name: null\n    - !Single\n      name:\n      - street\n      target_id: 146\n      target_name: null\n    inputs:\n    - id: 138\n      name: i\n      table:\n      - default_db\n      - invoices\n    - id: 135\n      name: ii\n      table:\n      - default_db\n      - invoice_items\n- - 1:281-323\n  - columns:\n    - !All\n      input_id: 138\n      except: []\n    - !All\n      input_id: 135\n      except: []\n    - !Single\n      name:\n      - total\n      target_id: 176\n      target_name: null\n    inputs:\n    - id: 138\n      name: i\n      table:\n      - default_db\n      - invoices\n    - id: 135\n      name: ii\n      table:\n      - default_db\n      - invoice_items\n- - 1:328-466\n  - columns:\n    - !Single\n      name:\n      - city\n      target_id: 149\n      target_name: null\n    - !Single\n      name:\n      - street\n      target_id: 150\n      target_name: null\n    - !Single\n      name:\n      - num_orders\n      target_id: 182\n      target_name: null\n    - !Single\n      name:\n      - num_tracks\n      target_id: 185\n      target_name: null\n    - !Single\n      name:\n      - total_price\n      target_id: 188\n      target_name: null\n    inputs:\n    - id: 138\n      name: i\n      table:\n      - default_db\n      - invoices\n    - id: 135\n      name: ii\n      table:\n      - default_db\n      - invoice_items\n- - 1:536-586\n  - columns:\n    - !Single\n      name:\n      - city\n      target_id: 195\n      target_name: null\n    - !Single\n      name:\n      - street\n      target_id: 150\n      target_name: null\n    - !Single\n      name:\n      - num_orders\n      target_id: 182\n      target_name: null\n    - !Single\n      name:\n      - num_tracks\n      target_id: 185\n      target_name: null\n    - !Single\n      name:\n      - total_price\n      target_id: 188\n      target_name: null\n    - !Single\n      name:\n      - running_total_num_tracks\n      target_id: 241\n      target_name: null\n    inputs:\n    - id: 138\n      name: i\n      table:\n      - default_db\n      - invoices\n    - id: 135\n      name: ii\n      table:\n      - default_db\n      - invoice_items\n- - 1:595-614\n  - columns:\n    - !Single\n      name:\n      - city\n      target_id: 195\n      target_name: null\n    - !Single\n      name:\n      - street\n      target_id: 150\n      target_name: null\n    - !Single\n      name:\n      - num_orders\n      target_id: 182\n      target_name: null\n    - !Single\n      name:\n      - num_tracks\n      target_id: 185\n      target_name: null\n    - !Single\n      name:\n      - total_price\n      target_id: 188\n      target_name: null\n    - !Single\n      name:\n      - running_total_num_tracks\n      target_id: 241\n      target_name: null\n    inputs:\n    - id: 138\n      name: i\n      table:\n      - default_db\n      - invoices\n    - id: 135\n      name: ii\n      table:\n      - default_db\n      - invoice_items\n- - 1:615-663\n  - columns:\n    - !Single\n      name:\n      - city\n      target_id: 195\n      target_name: null\n    - !Single\n      name:\n      - street\n      target_id: 150\n      target_name: null\n    - !Single\n      name:\n      - num_orders\n      target_id: 182\n      target_name: null\n    - !Single\n      name:\n      - num_tracks\n      target_id: 185\n      target_name: null\n    - !Single\n      name:\n      - total_price\n      target_id: 188\n      target_name: null\n    - !Single\n      name:\n      - running_total_num_tracks\n      target_id: 241\n      target_name: null\n    - !Single\n      name:\n      - num_tracks_last_week\n      target_id: 255\n      target_name: null\n    inputs:\n    - id: 138\n      name: i\n      table:\n      - default_db\n      - invoices\n    - id: 135\n      name: ii\n      table:\n      - default_db\n      - invoice_items\n- - 1:664-783\n  - columns:\n    - !Single\n      name:\n      - city\n      target_id: 261\n      target_name: null\n    - !Single\n      name:\n      - street\n      target_id: 262\n      target_name: null\n    - !Single\n      name:\n      - num_orders\n      target_id: 263\n      target_name: null\n    - !Single\n      name:\n      - num_tracks\n      target_id: 264\n      target_name: null\n    - !Single\n      name:\n      - running_total_num_tracks\n      target_id: 265\n      target_name: null\n    - !Single\n      name:\n      - num_tracks_last_week\n      target_id: 266\n      target_name: null\n    inputs:\n    - id: 138\n      name: i\n      table:\n      - default_db\n      - invoices\n    - id: 135\n      name: ii\n      table:\n      - default_db\n      - invoice_items\n- - 1:784-791\n  - columns:\n    - !Single\n      name:\n      - city\n      target_id: 261\n      target_name: null\n    - !Single\n      name:\n      - street\n      target_id: 262\n      target_name: null\n    - !Single\n      name:\n      - num_orders\n      target_id: 263\n      target_name: null\n    - !Single\n      name:\n      - num_tracks\n      target_id: 264\n      target_name: null\n    - !Single\n      name:\n      - running_total_num_tracks\n      target_id: 265\n      target_name: null\n    - !Single\n      name:\n      - num_tracks_last_week\n      target_id: 266\n      target_name: null\n    inputs:\n    - id: 138\n      name: i\n      table:\n      - default_db\n      - invoices\n    - id: 135\n      name: ii\n      table:\n      - default_db\n      - invoice_items\nnodes:\n- id: 135\n  kind: Ident\n  span: 1:155-168\n  ident: !Ident\n  - default_db\n  - invoice_items\n  parent: 144\n- id: 138\n  kind: Ident\n  span: 1:131-146\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 144\n- id: 140\n  kind: RqOperator\n  span: 1:170-182\n  targets:\n  - 142\n  - 143\n  parent: 144\n- id: 142\n  kind: Ident\n  span: 1:172-182\n  ident: !Ident\n  - this\n  - i\n  - invoice_id\n  targets:\n  - 138\n- id: 143\n  kind: Ident\n  span: 1:172-182\n  ident: !Ident\n  - that\n  - ii\n  - invoice_id\n  targets:\n  - 135\n- id: 144\n  kind: 'TransformCall: Join'\n  span: 1:147-183\n  children:\n  - 138\n  - 135\n  - 140\n  parent: 148\n- id: 145\n  kind: Ident\n  span: 1:204-218\n  alias: city\n  ident: !Ident\n  - this\n  - i\n  - billing_city\n  targets:\n  - 138\n  parent: 147\n- id: 146\n  kind: Ident\n  span: 1:233-250\n  alias: street\n  ident: !Ident\n  - this\n  - i\n  - billing_address\n  targets:\n  - 138\n  parent: 147\n- id: 147\n  kind: Tuple\n  span: 1:191-253\n  children:\n  - 145\n  - 146\n  parent: 148\n- id: 148\n  kind: 'TransformCall: Derive'\n  span: 1:184-253\n  children:\n  - 144\n  - 147\n  parent: 181\n- id: 149\n  kind: Ident\n  span: 1:261-265\n  ident: !Ident\n  - this\n  - city\n  targets:\n  - 145\n  parent: 151\n- id: 150\n  kind: Ident\n  span: 1:267-273\n  ident: !Ident\n  - this\n  - street\n  targets:\n  - 146\n  parent: 151\n- id: 151\n  kind: Tuple\n  span: 1:260-274\n  children:\n  - 149\n  - 150\n  parent: 192\n- id: 176\n  kind: RqOperator\n  span: 1:296-323\n  alias: total\n  targets:\n  - 178\n  - 179\n  parent: 180\n- id: 178\n  kind: Ident\n  span: 1:296-309\n  ident: !Ident\n  - this\n  - ii\n  - unit_price\n  targets:\n  - 135\n- id: 179\n  kind: Ident\n  span: 1:312-323\n  ident: !Ident\n  - this\n  - ii\n  - quantity\n  targets:\n  - 135\n- id: 180\n  kind: Tuple\n  span: 1:296-323\n  children:\n  - 176\n  parent: 181\n- id: 181\n  kind: 'TransformCall: Derive'\n  span: 1:281-323\n  children:\n  - 148\n  - 180\n  parent: 192\n- id: 182\n  kind: RqOperator\n  span: 1:361-388\n  alias: num_orders\n  targets:\n  - 184\n  parent: 191\n- id: 184\n  kind: Ident\n  span: 1:376-388\n  ident: !Ident\n  - this\n  - i\n  - invoice_id\n  targets:\n  - 138\n- id: 185\n  kind: RqOperator\n  span: 1:411-426\n  alias: num_tracks\n  targets:\n  - 187\n  parent: 191\n- id: 187\n  kind: Ident\n  span: 1:415-426\n  ident: !Ident\n  - this\n  - ii\n  - quantity\n  targets:\n  - 135\n- id: 188\n  kind: RqOperator\n  span: 1:450-459\n  alias: total_price\n  targets:\n  - 190\n  parent: 191\n- id: 190\n  kind: Ident\n  span: 1:454-459\n  ident: !Ident\n  - this\n  - total\n  targets:\n  - 176\n- id: 191\n  kind: Tuple\n  span: 1:338-466\n  children:\n  - 182\n  - 185\n  - 188\n  parent: 192\n- id: 192\n  kind: 'TransformCall: Aggregate'\n  span: 1:328-466\n  children:\n  - 181\n  - 191\n  - 151\n  parent: 245\n- id: 195\n  kind: Ident\n  span: 1:476-480\n  ident: !Ident\n  - this\n  - city\n  targets:\n  - 149\n  parent: 196\n- id: 196\n  kind: Tuple\n  span: 1:475-481\n  children:\n  - 195\n- id: 220\n  kind: Ident\n  span: 1:493-499\n  ident: !Ident\n  - this\n  - street\n  targets:\n  - 150\n- id: 241\n  kind: RqOperator\n  span: 1:571-585\n  alias: running_total_num_tracks\n  targets:\n  - 243\n  parent: 244\n- id: 243\n  kind: Ident\n  span: 1:575-585\n  ident: !Ident\n  - this\n  - num_tracks\n  targets:\n  - 185\n- id: 244\n  kind: Tuple\n  span: 1:543-586\n  children:\n  - 241\n  parent: 245\n- id: 245\n  kind: 'TransformCall: Derive'\n  span: 1:536-586\n  children:\n  - 192\n  - 244\n  parent: 254\n- id: 247\n  kind: Literal\n- id: 251\n  kind: Ident\n  span: 1:601-605\n  ident: !Ident\n  - this\n  - city\n  targets:\n  - 195\n  parent: 254\n- id: 252\n  kind: Ident\n  span: 1:607-613\n  ident: !Ident\n  - this\n  - street\n  targets:\n  - 150\n  parent: 254\n- id: 254\n  kind: 'TransformCall: Sort'\n  span: 1:595-614\n  children:\n  - 245\n  - 251\n  - 252\n  parent: 260\n- id: 255\n  kind: RqOperator\n  span: 1:646-662\n  alias: num_tracks_last_week\n  targets:\n  - 257\n  - 258\n  parent: 259\n- id: 257\n  kind: Literal\n  span: 1:650-651\n- id: 258\n  kind: Ident\n  span: 1:652-662\n  ident: !Ident\n  - this\n  - num_tracks\n  targets:\n  - 185\n- id: 259\n  kind: Tuple\n  span: 1:622-663\n  children:\n  - 255\n  parent: 260\n- id: 260\n  kind: 'TransformCall: Derive'\n  span: 1:615-663\n  children:\n  - 254\n  - 259\n  parent: 268\n- id: 261\n  kind: Ident\n  span: 1:677-681\n  ident: !Ident\n  - this\n  - city\n  targets:\n  - 195\n  parent: 267\n- id: 262\n  kind: Ident\n  span: 1:687-693\n  ident: !Ident\n  - this\n  - street\n  targets:\n  - 150\n  parent: 267\n- id: 263\n  kind: Ident\n  span: 1:699-709\n  ident: !Ident\n  - this\n  - num_orders\n  targets:\n  - 182\n  parent: 267\n- id: 264\n  kind: Ident\n  span: 1:715-725\n  ident: !Ident\n  - this\n  - num_tracks\n  targets:\n  - 185\n  parent: 267\n- id: 265\n  kind: Ident\n  span: 1:731-755\n  ident: !Ident\n  - this\n  - running_total_num_tracks\n  targets:\n  - 241\n  parent: 267\n- id: 266\n  kind: Ident\n  span: 1:761-781\n  ident: !Ident\n  - this\n  - num_tracks_last_week\n  targets:\n  - 255\n  parent: 267\n- id: 267\n  kind: Tuple\n  span: 1:671-783\n  children:\n  - 261\n  - 262\n  - 263\n  - 264\n  - 265\n  - 266\n  parent: 268\n- id: 268\n  kind: 'TransformCall: Select'\n  span: 1:664-783\n  children:\n  - 260\n  - 267\n  parent: 270\n- id: 270\n  kind: 'TransformCall: Take'\n  span: 1:784-791\n  children:\n  - 268\n  - 271\n- id: 271\n  kind: Literal\n  parent: 270\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:131-135\n              args:\n              - Ident:\n                - invoices\n                span: 1:138-146\n                alias: i\n            span: 1:131-146\n          - FuncCall:\n              name:\n                Ident:\n                - join\n                span: 1:147-151\n              args:\n              - Ident:\n                - invoice_items\n                span: 1:155-168\n                alias: ii\n              - Unary:\n                  op: EqSelf\n                  expr:\n                    Ident:\n                    - invoice_id\n                    span: 1:172-182\n                span: 1:170-182\n            span: 1:147-183\n          - FuncCall:\n              name:\n                Ident:\n                - derive\n                span: 1:184-190\n              args:\n              - Tuple:\n                - Ident:\n                  - i\n                  - billing_city\n                  span: 1:204-218\n                  alias: city\n                - Ident:\n                  - i\n                  - billing_address\n                  span: 1:233-250\n                  alias: street\n                span: 1:191-253\n            span: 1:184-253\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:254-259\n              args:\n              - Tuple:\n                - Ident:\n                  - city\n                  span: 1:261-265\n                - Ident:\n                  - street\n                  span: 1:267-273\n                span: 1:260-274\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - derive\n                        span: 1:281-287\n                      args:\n                      - Binary:\n                          left:\n                            Ident:\n                            - ii\n                            - unit_price\n                            span: 1:296-309\n                          op: Mul\n                          right:\n                            Ident:\n                            - ii\n                            - quantity\n                            span: 1:312-323\n                        span: 1:296-323\n                        alias: total\n                    span: 1:281-323\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - aggregate\n                        span: 1:328-337\n                      args:\n                      - Tuple:\n                        - FuncCall:\n                            name:\n                              Ident:\n                              - count_distinct\n                              span: 1:361-375\n                            args:\n                            - Ident:\n                              - i\n                              - invoice_id\n                              span: 1:376-388\n                          span: 1:361-388\n                          alias: num_orders\n                        - FuncCall:\n                            name:\n                              Ident:\n                              - sum\n                              span: 1:411-414\n                            args:\n                            - Ident:\n                              - ii\n                              - quantity\n                              span: 1:415-426\n                          span: 1:411-426\n                          alias: num_tracks\n                        - FuncCall:\n                            name:\n                              Ident:\n                              - sum\n                              span: 1:450-453\n                            args:\n                            - Ident:\n                              - total\n                              span: 1:454-459\n                          span: 1:450-459\n                          alias: total_price\n                        span: 1:338-466\n                    span: 1:328-466\n                span: 1:281-466\n            span: 1:254-468\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:469-474\n              args:\n              - Tuple:\n                - Ident:\n                  - city\n                  span: 1:476-480\n                span: 1:475-481\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - sort\n                        span: 1:488-492\n                      args:\n                      - Ident:\n                        - street\n                        span: 1:493-499\n                    span: 1:488-499\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - window\n                        span: 1:504-510\n                      args:\n                      - FuncCall:\n                          name:\n                            Ident:\n                            - derive\n                            span: 1:536-542\n                          args:\n                          - Tuple:\n                            - FuncCall:\n                                name:\n                                  Ident:\n                                  - sum\n                                  span: 1:571-574\n                                args:\n                                - Ident:\n                                  - num_tracks\n                                  span: 1:575-585\n                              span: 1:571-585\n                              alias: running_total_num_tracks\n                            span: 1:543-586\n                        span: 1:536-586\n                      named_args:\n                        expanding:\n                          Literal:\n                            Boolean: true\n                          span: 1:521-525\n                    span: 1:504-592\n                span: 1:488-592\n            span: 1:469-594\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:595-599\n              args:\n              - Tuple:\n                - Ident:\n                  - city\n                  span: 1:601-605\n                - Ident:\n                  - street\n                  span: 1:607-613\n                span: 1:600-614\n            span: 1:595-614\n          - FuncCall:\n              name:\n                Ident:\n                - derive\n                span: 1:615-621\n              args:\n              - Tuple:\n                - FuncCall:\n                    name:\n                      Ident:\n                      - lag\n                      span: 1:646-649\n                    args:\n                    - Literal:\n                        Integer: 7\n                      span: 1:650-651\n                    - Ident:\n                      - num_tracks\n                      span: 1:652-662\n                  span: 1:646-662\n                  alias: num_tracks_last_week\n                span: 1:622-663\n            span: 1:615-663\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:664-670\n              args:\n              - Tuple:\n                - Ident:\n                  - city\n                  span: 1:677-681\n                - Ident:\n                  - street\n                  span: 1:687-693\n                - Ident:\n                  - num_orders\n                  span: 1:699-709\n                - Ident:\n                  - num_tracks\n                  span: 1:715-725\n                - Ident:\n                  - running_total_num_tracks\n                  span: 1:731-755\n                - Ident:\n                  - num_tracks_last_week\n                  span: 1:761-781\n                span: 1:671-783\n            span: 1:664-783\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:784-788\n              args:\n              - Literal:\n                  Integer: 20\n                span: 1:789-791\n            span: 1:784-791\n        span: 1:131-791\n    span: 1:130-791\n    doc_comment: ' Calculate a number of metrics about the sales of tracks in each city.'\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__loop_01.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (DB::Exception: Syntax error)\\n# glaredb:skip (DataFusion does not support recursive CTEs https://github.com/apache/arrow-datafusion/issues/462)\\nfrom [{n = 1}]\\nselect n = n - 2\\nloop (filter n < 4 | select n = n + 1)\\nselect n = n * 2\\nsort n\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/loop_01.prql\n---\nframes:\n- - 1:177-193\n  - columns:\n    - !Single\n      name:\n      - n\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 125\n      name: _literal_125\n      table:\n      - default_db\n      - _literal_125\n- - 1:200-212\n  - columns:\n    - !Single\n      name:\n      - n\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 125\n      name: _literal_125\n      table:\n      - default_db\n      - _literal_125\n- - 1:215-231\n  - columns:\n    - !Single\n      name:\n      - n\n      target_id: 152\n      target_name: null\n    inputs:\n    - id: 125\n      name: _literal_125\n      table:\n      - default_db\n      - _literal_125\n- - 1:194-232\n  - columns:\n    - !Single\n      name:\n      - n\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 125\n      name: _literal_125\n      table:\n      - default_db\n      - _literal_125\n- - 1:233-249\n  - columns:\n    - !Single\n      name:\n      - n\n      target_id: 160\n      target_name: null\n    inputs:\n    - id: 125\n      name: _literal_125\n      table:\n      - default_db\n      - _literal_125\n- - 1:250-256\n  - columns:\n    - !Single\n      name:\n      - n\n      target_id: 160\n      target_name: null\n    inputs:\n    - id: 125\n      name: _literal_125\n      table:\n      - default_db\n      - _literal_125\nnodes:\n- id: 125\n  kind: Array\n  span: 1:162-176\n  children:\n  - 126\n  parent: 134\n- id: 126\n  kind: Tuple\n  span: 1:168-175\n  children:\n  - 127\n  parent: 125\n- id: 127\n  kind: Literal\n  span: 1:173-174\n  alias: n\n  parent: 126\n- id: 129\n  kind: RqOperator\n  span: 1:188-193\n  alias: n\n  targets:\n  - 131\n  - 132\n  parent: 133\n- id: 131\n  kind: Ident\n  span: 1:188-189\n  ident: !Ident\n  - this\n  - _literal_125\n  - n\n  targets:\n  - 125\n- id: 132\n  kind: Literal\n  span: 1:192-193\n- id: 133\n  kind: Tuple\n  span: 1:188-193\n  children:\n  - 129\n  parent: 134\n- id: 134\n  kind: 'TransformCall: Select'\n  span: 1:177-193\n  children:\n  - 125\n  - 133\n  parent: 158\n- id: 143\n  kind: Ident\n  ident: !Ident\n  - _param\n  - _tbl\n  targets:\n  - 140\n  parent: 151\n- id: 147\n  kind: RqOperator\n  span: 1:207-212\n  targets:\n  - 149\n  - 150\n  parent: 151\n- id: 149\n  kind: Ident\n  span: 1:207-208\n  ident: !Ident\n  - this\n  - n\n  targets:\n  - 129\n- id: 150\n  kind: Literal\n  span: 1:211-212\n- id: 151\n  kind: 'TransformCall: Filter'\n  span: 1:200-212\n  children:\n  - 143\n  - 147\n  parent: 157\n- id: 152\n  kind: RqOperator\n  span: 1:226-231\n  alias: n\n  targets:\n  - 154\n  - 155\n  parent: 156\n- id: 154\n  kind: Ident\n  span: 1:226-227\n  ident: !Ident\n  - this\n  - n\n  targets:\n  - 129\n- id: 155\n  kind: Literal\n  span: 1:230-231\n- id: 156\n  kind: Tuple\n  span: 1:226-231\n  children:\n  - 152\n  parent: 157\n- id: 157\n  kind: 'TransformCall: Select'\n  span: 1:215-231\n  children:\n  - 151\n  - 156\n- id: 158\n  kind: 'TransformCall: Loop'\n  span: 1:194-232\n  children:\n  - 134\n  - 159\n  parent: 165\n- id: 159\n  kind: Func\n  span: 1:215-231\n  parent: 158\n- id: 160\n  kind: RqOperator\n  span: 1:244-249\n  alias: n\n  targets:\n  - 162\n  - 163\n  parent: 164\n- id: 162\n  kind: Ident\n  span: 1:244-245\n  ident: !Ident\n  - this\n  - n\n  targets:\n  - 129\n- id: 163\n  kind: Literal\n  span: 1:248-249\n- id: 164\n  kind: Tuple\n  span: 1:244-249\n  children:\n  - 160\n  parent: 165\n- id: 165\n  kind: 'TransformCall: Select'\n  span: 1:233-249\n  children:\n  - 158\n  - 164\n  parent: 168\n- id: 166\n  kind: Ident\n  span: 1:255-256\n  ident: !Ident\n  - this\n  - n\n  targets:\n  - 160\n  parent: 168\n- id: 168\n  kind: 'TransformCall: Sort'\n  span: 1:250-256\n  children:\n  - 165\n  - 166\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:162-166\n              args:\n              - Array:\n                - Tuple:\n                  - Literal:\n                      Integer: 1\n                    span: 1:173-174\n                    alias: n\n                  span: 1:168-175\n                span: 1:167-176\n            span: 1:162-176\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:177-183\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - n\n                    span: 1:188-189\n                  op: Sub\n                  right:\n                    Literal:\n                      Integer: 2\n                    span: 1:192-193\n                span: 1:188-193\n                alias: n\n            span: 1:177-193\n          - FuncCall:\n              name:\n                Ident:\n                - loop\n                span: 1:194-198\n              args:\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - filter\n                        span: 1:200-206\n                      args:\n                      - Binary:\n                          left:\n                            Ident:\n                            - n\n                            span: 1:207-208\n                          op: Lt\n                          right:\n                            Literal:\n                              Integer: 4\n                            span: 1:211-212\n                        span: 1:207-212\n                    span: 1:200-212\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - select\n                        span: 1:215-221\n                      args:\n                      - Binary:\n                          left:\n                            Ident:\n                            - n\n                            span: 1:226-227\n                          op: Add\n                          right:\n                            Literal:\n                              Integer: 1\n                            span: 1:230-231\n                        span: 1:226-231\n                        alias: n\n                    span: 1:215-231\n                span: 1:200-231\n            span: 1:194-232\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:233-239\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - n\n                    span: 1:244-245\n                  op: Mul\n                  right:\n                    Literal:\n                      Integer: 2\n                    span: 1:248-249\n                span: 1:244-249\n                alias: n\n            span: 1:233-249\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:250-254\n              args:\n              - Ident:\n                - n\n                span: 1:255-256\n            span: 1:250-256\n        span: 1:162-256\n    span: 1:0-256\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__math_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# sqlite:skip (see https://github.com/rusqlite/rusqlite/issues/1211)\\nfrom invoices\\ntake 5\\nselect {\\n    total_original = (total | math.round 2),\\n    total_x = (math.pi - total | math.round 2 | math.abs),\\n    total_floor = (math.floor total),\\n    total_ceil = (math.ceil total),\\n    total_log10 = (math.log10 total | math.round 3),\\n    total_log2 = (math.log 2 total | math.round 3),\\n    total_sqrt = (math.sqrt total | math.round 3),\\n    total_ln = (math.ln total | math.exp | math.round 2),\\n    total_cos = (math.cos total | math.acos | math.round 2),\\n    total_sin = (math.sin total | math.asin | math.round 2),\\n    total_tan = (math.tan total | math.atan | math.round 2),\\n    total_deg = (total | math.degrees | math.radians | math.round 2),\\n    total_square = (total | math.pow 2 | math.round 2),\\n    total_square_op = ((total ** 2) | math.round 2),\\n}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/math_module.prql\n---\nframes:\n- - 1:96-102\n  - columns:\n    - !All\n      input_id: 119\n      except: []\n    inputs:\n    - id: 119\n      name: invoices\n      table:\n      - default_db\n      - invoices\n- - 1:103-867\n  - columns:\n    - !Single\n      name:\n      - total_original\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - total_x\n      target_id: 129\n      target_name: null\n    - !Single\n      name:\n      - total_floor\n      target_id: 140\n      target_name: null\n    - !Single\n      name:\n      - total_ceil\n      target_id: 143\n      target_name: null\n    - !Single\n      name:\n      - total_log10\n      target_id: 146\n      target_name: null\n    - !Single\n      name:\n      - total_log2\n      target_id: 153\n      target_name: null\n    - !Single\n      name:\n      - total_sqrt\n      target_id: 161\n      target_name: null\n    - !Single\n      name:\n      - total_ln\n      target_id: 168\n      target_name: null\n    - !Single\n      name:\n      - total_cos\n      target_id: 177\n      target_name: null\n    - !Single\n      name:\n      - total_sin\n      target_id: 186\n      target_name: null\n    - !Single\n      name:\n      - total_tan\n      target_id: 195\n      target_name: null\n    - !Single\n      name:\n      - total_deg\n      target_id: 204\n      target_name: null\n    - !Single\n      name:\n      - total_square\n      target_id: 213\n      target_name: null\n    - !Single\n      name:\n      - total_square_op\n      target_id: 222\n      target_name: null\n    inputs:\n    - id: 119\n      name: invoices\n      table:\n      - default_db\n      - invoices\nnodes:\n- id: 119\n  kind: Ident\n  span: 1:82-95\n  ident: !Ident\n  - default_db\n  - invoices\n  parent: 122\n- id: 122\n  kind: 'TransformCall: Take'\n  span: 1:96-102\n  children:\n  - 119\n  - 123\n  parent: 231\n- id: 123\n  kind: Literal\n  parent: 122\n- id: 124\n  kind: RqOperator\n  span: 1:142-154\n  alias: total_original\n  targets:\n  - 127\n  - 128\n  parent: 230\n- id: 127\n  kind: Literal\n  span: 1:153-154\n- id: 128\n  kind: Ident\n  span: 1:134-139\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 129\n  kind: RqOperator\n  span: 1:205-213\n  alias: total_x\n  targets:\n  - 131\n  parent: 230\n- id: 131\n  kind: RqOperator\n  span: 1:190-202\n  targets:\n  - 134\n  - 135\n- id: 134\n  kind: Literal\n  span: 1:201-202\n- id: 135\n  kind: RqOperator\n  span: 1:172-187\n  targets:\n  - 138\n  - 139\n- id: 138\n  kind: RqOperator\n  span: 1:172-179\n- id: 139\n  kind: Ident\n  span: 1:182-187\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 140\n  kind: RqOperator\n  span: 1:234-252\n  alias: total_floor\n  targets:\n  - 142\n  parent: 230\n- id: 142\n  kind: Ident\n  span: 1:246-251\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 143\n  kind: RqOperator\n  span: 1:271-288\n  alias: total_ceil\n  targets:\n  - 145\n  parent: 230\n- id: 145\n  kind: Ident\n  span: 1:282-287\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 146\n  kind: RqOperator\n  span: 1:328-340\n  alias: total_log10\n  targets:\n  - 149\n  - 150\n  parent: 230\n- id: 149\n  kind: Literal\n  span: 1:339-340\n- id: 150\n  kind: RqOperator\n  span: 1:309-325\n  targets:\n  - 152\n- id: 152\n  kind: Ident\n  span: 1:320-325\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 153\n  kind: RqOperator\n  span: 1:380-392\n  alias: total_log2\n  targets:\n  - 156\n  - 157\n  parent: 230\n- id: 156\n  kind: Literal\n  span: 1:391-392\n- id: 157\n  kind: RqOperator\n  span: 1:361-377\n  targets:\n  - 159\n  - 160\n- id: 159\n  kind: Literal\n  span: 1:370-371\n- id: 160\n  kind: Ident\n  span: 1:372-377\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 161\n  kind: RqOperator\n  span: 1:431-443\n  alias: total_sqrt\n  targets:\n  - 164\n  - 165\n  parent: 230\n- id: 164\n  kind: Literal\n  span: 1:442-443\n- id: 165\n  kind: RqOperator\n  span: 1:413-428\n  targets:\n  - 167\n- id: 167\n  kind: Ident\n  span: 1:423-428\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 168\n  kind: RqOperator\n  span: 1:489-501\n  alias: total_ln\n  targets:\n  - 171\n  - 172\n  parent: 230\n- id: 171\n  kind: Literal\n  span: 1:500-501\n- id: 172\n  kind: RqOperator\n  span: 1:478-486\n  targets:\n  - 174\n- id: 174\n  kind: RqOperator\n  span: 1:462-475\n  targets:\n  - 176\n- id: 176\n  kind: Ident\n  span: 1:470-475\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 177\n  kind: RqOperator\n  span: 1:550-562\n  alias: total_cos\n  targets:\n  - 180\n  - 181\n  parent: 230\n- id: 180\n  kind: Literal\n  span: 1:561-562\n- id: 181\n  kind: RqOperator\n  span: 1:538-547\n  targets:\n  - 183\n- id: 183\n  kind: RqOperator\n  span: 1:521-535\n  targets:\n  - 185\n- id: 185\n  kind: Ident\n  span: 1:530-535\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 186\n  kind: RqOperator\n  span: 1:611-623\n  alias: total_sin\n  targets:\n  - 189\n  - 190\n  parent: 230\n- id: 189\n  kind: Literal\n  span: 1:622-623\n- id: 190\n  kind: RqOperator\n  span: 1:599-608\n  targets:\n  - 192\n- id: 192\n  kind: RqOperator\n  span: 1:582-596\n  targets:\n  - 194\n- id: 194\n  kind: Ident\n  span: 1:591-596\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 195\n  kind: RqOperator\n  span: 1:672-684\n  alias: total_tan\n  targets:\n  - 198\n  - 199\n  parent: 230\n- id: 198\n  kind: Literal\n  span: 1:683-684\n- id: 199\n  kind: RqOperator\n  span: 1:660-669\n  targets:\n  - 201\n- id: 201\n  kind: RqOperator\n  span: 1:643-657\n  targets:\n  - 203\n- id: 203\n  kind: Ident\n  span: 1:652-657\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 204\n  kind: RqOperator\n  span: 1:742-754\n  alias: total_deg\n  targets:\n  - 207\n  - 208\n  parent: 230\n- id: 207\n  kind: Literal\n  span: 1:753-754\n- id: 208\n  kind: RqOperator\n  span: 1:727-739\n  targets:\n  - 210\n- id: 210\n  kind: RqOperator\n  span: 1:712-724\n  targets:\n  - 212\n- id: 212\n  kind: Ident\n  span: 1:704-709\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 213\n  kind: RqOperator\n  span: 1:798-810\n  alias: total_square\n  targets:\n  - 216\n  - 217\n  parent: 230\n- id: 216\n  kind: Literal\n  span: 1:809-810\n- id: 217\n  kind: RqOperator\n  span: 1:785-795\n  targets:\n  - 220\n  - 221\n- id: 220\n  kind: Literal\n  span: 1:794-795\n- id: 221\n  kind: Ident\n  span: 1:777-782\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 222\n  kind: RqOperator\n  span: 1:851-863\n  alias: total_square_op\n  targets:\n  - 225\n  - 226\n  parent: 230\n- id: 225\n  kind: Literal\n  span: 1:862-863\n- id: 226\n  kind: RqOperator\n  span: 1:836-848\n  targets:\n  - 228\n  - 229\n- id: 228\n  kind: Literal\n  span: 1:846-847\n- id: 229\n  kind: Ident\n  span: 1:837-842\n  ident: !Ident\n  - this\n  - invoices\n  - total\n  targets:\n  - 119\n- id: 230\n  kind: Tuple\n  span: 1:110-867\n  children:\n  - 124\n  - 129\n  - 140\n  - 143\n  - 146\n  - 153\n  - 161\n  - 168\n  - 177\n  - 186\n  - 195\n  - 204\n  - 213\n  - 222\n  parent: 231\n- id: 231\n  kind: 'TransformCall: Select'\n  span: 1:103-867\n  children:\n  - 122\n  - 230\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:82-86\n              args:\n              - Ident:\n                - invoices\n                span: 1:87-95\n            span: 1:82-95\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:96-100\n              args:\n              - Literal:\n                  Integer: 5\n                span: 1:101-102\n            span: 1:96-102\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:103-109\n              args:\n              - Tuple:\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - total\n                      span: 1:134-139\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:142-152\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:153-154\n                      span: 1:142-154\n                  span: 1:133-155\n                  alias: total_original\n                - Pipeline:\n                    exprs:\n                    - Binary:\n                        left:\n                          Ident:\n                          - math\n                          - pi\n                          span: 1:172-179\n                        op: Sub\n                        right:\n                          Ident:\n                          - total\n                          span: 1:182-187\n                      span: 1:172-187\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:190-200\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:201-202\n                      span: 1:190-202\n                    - Ident:\n                      - math\n                      - abs\n                      span: 1:205-213\n                  span: 1:171-214\n                  alias: total_x\n                - FuncCall:\n                    name:\n                      Ident:\n                      - math\n                      - floor\n                      span: 1:235-245\n                    args:\n                    - Ident:\n                      - total\n                      span: 1:246-251\n                  span: 1:234-252\n                  alias: total_floor\n                - FuncCall:\n                    name:\n                      Ident:\n                      - math\n                      - ceil\n                      span: 1:272-281\n                    args:\n                    - Ident:\n                      - total\n                      span: 1:282-287\n                  span: 1:271-288\n                  alias: total_ceil\n                - Pipeline:\n                    exprs:\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - log10\n                          span: 1:309-319\n                        args:\n                        - Ident:\n                          - total\n                          span: 1:320-325\n                      span: 1:309-325\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:328-338\n                        args:\n                        - Literal:\n                            Integer: 3\n                          span: 1:339-340\n                      span: 1:328-340\n                  span: 1:308-341\n                  alias: total_log10\n                - Pipeline:\n                    exprs:\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - log\n                          span: 1:361-369\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:370-371\n                        - Ident:\n                          - total\n                          span: 1:372-377\n                      span: 1:361-377\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:380-390\n                        args:\n                        - Literal:\n                            Integer: 3\n                          span: 1:391-392\n                      span: 1:380-392\n                  span: 1:360-393\n                  alias: total_log2\n                - Pipeline:\n                    exprs:\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - sqrt\n                          span: 1:413-422\n                        args:\n                        - Ident:\n                          - total\n                          span: 1:423-428\n                      span: 1:413-428\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:431-441\n                        args:\n                        - Literal:\n                            Integer: 3\n                          span: 1:442-443\n                      span: 1:431-443\n                  span: 1:412-444\n                  alias: total_sqrt\n                - Pipeline:\n                    exprs:\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - ln\n                          span: 1:462-469\n                        args:\n                        - Ident:\n                          - total\n                          span: 1:470-475\n                      span: 1:462-475\n                    - Ident:\n                      - math\n                      - exp\n                      span: 1:478-486\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:489-499\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:500-501\n                      span: 1:489-501\n                  span: 1:461-502\n                  alias: total_ln\n                - Pipeline:\n                    exprs:\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - cos\n                          span: 1:521-529\n                        args:\n                        - Ident:\n                          - total\n                          span: 1:530-535\n                      span: 1:521-535\n                    - Ident:\n                      - math\n                      - acos\n                      span: 1:538-547\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:550-560\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:561-562\n                      span: 1:550-562\n                  span: 1:520-563\n                  alias: total_cos\n                - Pipeline:\n                    exprs:\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - sin\n                          span: 1:582-590\n                        args:\n                        - Ident:\n                          - total\n                          span: 1:591-596\n                      span: 1:582-596\n                    - Ident:\n                      - math\n                      - asin\n                      span: 1:599-608\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:611-621\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:622-623\n                      span: 1:611-623\n                  span: 1:581-624\n                  alias: total_sin\n                - Pipeline:\n                    exprs:\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - tan\n                          span: 1:643-651\n                        args:\n                        - Ident:\n                          - total\n                          span: 1:652-657\n                      span: 1:643-657\n                    - Ident:\n                      - math\n                      - atan\n                      span: 1:660-669\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:672-682\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:683-684\n                      span: 1:672-684\n                  span: 1:642-685\n                  alias: total_tan\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - total\n                      span: 1:704-709\n                    - Ident:\n                      - math\n                      - degrees\n                      span: 1:712-724\n                    - Ident:\n                      - math\n                      - radians\n                      span: 1:727-739\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:742-752\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:753-754\n                      span: 1:742-754\n                  span: 1:703-755\n                  alias: total_deg\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - total\n                      span: 1:777-782\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - pow\n                          span: 1:785-793\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:794-795\n                      span: 1:785-795\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:798-808\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:809-810\n                      span: 1:798-810\n                  span: 1:776-811\n                  alias: total_square\n                - Pipeline:\n                    exprs:\n                    - Binary:\n                        left:\n                          Ident:\n                          - total\n                          span: 1:837-842\n                        op: Pow\n                        right:\n                          Literal:\n                            Integer: 2\n                          span: 1:846-847\n                      span: 1:836-848\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - math\n                          - round\n                          span: 1:851-861\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:862-863\n                      span: 1:851-863\n                  span: 1:835-864\n                  alias: total_square_op\n                span: 1:110-867\n            span: 1:103-867\n        span: 1:82-867\n    span: 1:0-867\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__pipelines.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip (Only works on Sqlite implementations which have the extension\\n# installed\\n# https://stackoverflow.com/questions/24037982/how-to-use-regexp-in-sqlite)\\n\\nfrom tracks\\n\\nfilter (name ~= \\\"Love\\\")\\nfilter ((milliseconds / 1000 / 60) | in 3..4)\\nsort track_id\\ntake 1..15\\nselect {name, composer}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/pipelines.prql\n---\nframes:\n- - 1:179-202\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:203-248\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:249-262\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:263-273\n  - columns:\n    - !All\n      input_id: 128\n      except: []\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:274-297\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - name\n      target_id: 162\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - composer\n      target_id: 163\n      target_name: null\n    inputs:\n    - id: 128\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 128\n  kind: Ident\n  span: 1:166-177\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 134\n- id: 130\n  kind: RqOperator\n  span: 1:187-201\n  targets:\n  - 132\n  - 133\n  parent: 134\n- id: 132\n  kind: Ident\n  span: 1:187-191\n  ident: !Ident\n  - this\n  - tracks\n  - name\n  targets:\n  - 128\n- id: 133\n  kind: Literal\n  span: 1:195-201\n- id: 134\n  kind: 'TransformCall: Filter'\n  span: 1:179-202\n  children:\n  - 128\n  - 130\n  parent: 154\n- id: 138\n  kind: Literal\n  span: 1:243-244\n  alias: start\n- id: 139\n  kind: Literal\n  span: 1:246-247\n  alias: end\n- id: 141\n  kind: RqOperator\n  span: 1:211-237\n  targets:\n  - 143\n  - 147\n- id: 143\n  kind: RqOperator\n  span: 1:212-231\n  targets:\n  - 145\n  - 146\n- id: 145\n  kind: Ident\n  span: 1:212-224\n  ident: !Ident\n  - this\n  - tracks\n  - milliseconds\n  targets:\n  - 128\n- id: 146\n  kind: Literal\n  span: 1:227-231\n- id: 147\n  kind: Literal\n  span: 1:234-236\n- id: 148\n  kind: RqOperator\n  span: 1:240-247\n  targets:\n  - 150\n  - 152\n  parent: 154\n- id: 150\n  kind: RqOperator\n  targets:\n  - 141\n  - 138\n- id: 152\n  kind: RqOperator\n  targets:\n  - 141\n  - 139\n- id: 154\n  kind: 'TransformCall: Filter'\n  span: 1:203-248\n  children:\n  - 134\n  - 148\n  parent: 157\n- id: 155\n  kind: Ident\n  span: 1:254-262\n  ident: !Ident\n  - this\n  - tracks\n  - track_id\n  targets:\n  - 128\n  parent: 157\n- id: 157\n  kind: 'TransformCall: Sort'\n  span: 1:249-262\n  children:\n  - 154\n  - 155\n  parent: 161\n- id: 158\n  kind: Literal\n  span: 1:268-269\n  alias: start\n  parent: 161\n- id: 159\n  kind: Literal\n  span: 1:271-273\n  alias: end\n  parent: 161\n- id: 161\n  kind: 'TransformCall: Take'\n  span: 1:263-273\n  children:\n  - 157\n  - 158\n  - 159\n  parent: 165\n- id: 162\n  kind: Ident\n  span: 1:282-286\n  ident: !Ident\n  - this\n  - tracks\n  - name\n  targets:\n  - 128\n  parent: 164\n- id: 163\n  kind: Ident\n  span: 1:288-296\n  ident: !Ident\n  - this\n  - tracks\n  - composer\n  targets:\n  - 128\n  parent: 164\n- id: 164\n  kind: Tuple\n  span: 1:281-297\n  children:\n  - 162\n  - 163\n  parent: 165\n- id: 165\n  kind: 'TransformCall: Select'\n  span: 1:274-297\n  children:\n  - 161\n  - 164\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:166-170\n              args:\n              - Ident:\n                - tracks\n                span: 1:171-177\n            span: 1:166-177\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:179-185\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - name\n                    span: 1:187-191\n                  op: RegexSearch\n                  right:\n                    Literal:\n                      String: Love\n                    span: 1:195-201\n                span: 1:187-201\n            span: 1:179-202\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:203-209\n              args:\n              - Pipeline:\n                  exprs:\n                  - Binary:\n                      left:\n                        Binary:\n                          left:\n                            Ident:\n                            - milliseconds\n                            span: 1:212-224\n                          op: DivFloat\n                          right:\n                            Literal:\n                              Integer: 1000\n                            span: 1:227-231\n                        span: 1:212-231\n                      op: DivFloat\n                      right:\n                        Literal:\n                          Integer: 60\n                        span: 1:234-236\n                    span: 1:211-237\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - in\n                        span: 1:240-242\n                      args:\n                      - Range:\n                          start:\n                            Literal:\n                              Integer: 3\n                            span: 1:243-244\n                          end:\n                            Literal:\n                              Integer: 4\n                            span: 1:246-247\n                        span: 1:243-247\n                    span: 1:240-247\n                span: 1:211-247\n            span: 1:203-248\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:249-253\n              args:\n              - Ident:\n                - track_id\n                span: 1:254-262\n            span: 1:249-262\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:263-267\n              args:\n              - Range:\n                  start:\n                    Literal:\n                      Integer: 1\n                    span: 1:268-269\n                  end:\n                    Literal:\n                      Integer: 15\n                    span: 1:271-273\n                span: 1:268-273\n            span: 1:263-273\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:274-280\n              args:\n              - Tuple:\n                - Ident:\n                  - name\n                  span: 1:282-286\n                - Ident:\n                  - composer\n                  span: 1:288-296\n                span: 1:281-297\n            span: 1:274-297\n        span: 1:166-297\n    span: 1:0-297\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__read_csv.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip\\n# postgres:skip\\n# mysql:skip\\nfrom (read_csv \\\"data_file_root/media_types.csv\\\")\\nappend (read_json \\\"data_file_root/media_types.json\\\")\\nsort media_type_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/read_csv.prql\n---\nframes:\n- - 1:92-144\n  - columns:\n    - !All\n      input_id: 122\n      except: []\n    inputs:\n    - id: 122\n      name: _literal_122\n      table:\n      - default_db\n      - _literal_122\n    - id: 117\n      name: _literal_117\n      table:\n      - default_db\n      - _literal_117\n- - 1:145-163\n  - columns:\n    - !All\n      input_id: 122\n      except: []\n    inputs:\n    - id: 122\n      name: _literal_122\n      table:\n      - default_db\n      - _literal_122\n    - id: 117\n      name: _literal_117\n      table:\n      - default_db\n      - _literal_117\nnodes:\n- id: 117\n  kind: RqOperator\n  span: 1:100-143\n  targets:\n  - 119\n  parent: 126\n- id: 119\n  kind: Literal\n  span: 1:110-143\n- id: 122\n  kind: RqOperator\n  span: 1:43-91\n  targets:\n  - 124\n  parent: 126\n- id: 124\n  kind: Literal\n  span: 1:58-90\n- id: 126\n  kind: 'TransformCall: Append'\n  span: 1:92-144\n  children:\n  - 122\n  - 117\n  parent: 129\n- id: 127\n  kind: Ident\n  span: 1:150-163\n  ident: !Ident\n  - this\n  - _literal_122\n  - media_type_id\n  targets:\n  - 122\n  parent: 129\n- id: 129\n  kind: 'TransformCall: Sort'\n  span: 1:145-163\n  children:\n  - 126\n  - 127\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:43-47\n              args:\n              - FuncCall:\n                  name:\n                    Ident:\n                    - read_csv\n                    span: 1:49-57\n                  args:\n                  - Literal:\n                      String: data_file_root/media_types.csv\n                    span: 1:58-90\n                span: 1:49-90\n            span: 1:43-91\n          - FuncCall:\n              name:\n                Ident:\n                - append\n                span: 1:92-98\n              args:\n              - FuncCall:\n                  name:\n                    Ident:\n                    - read_json\n                    span: 1:100-109\n                  args:\n                  - Literal:\n                      String: data_file_root/media_types.json\n                    span: 1:110-143\n                span: 1:100-143\n            span: 1:92-144\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:145-149\n              args:\n              - Ident:\n                - media_type_id\n                span: 1:150-163\n            span: 1:145-163\n        span: 1:43-163\n    span: 1:0-163\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__set_ops_remove.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nlet distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\\n\\nfrom_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2], [2], [3]] }'\\ndistinct\\nremove (from_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2]] }')\\nsort a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/set_ops_remove.prql\n---\nframes:\n- - 1:71-77\n  - columns:\n    - !Single\n      name:\n      - t\n      - a\n      target_id: 134\n      target_name: null\n    inputs:\n    - id: 125\n      name: t\n      table:\n      - default_db\n      - _literal_125\n- - 0:3163-3240\n  - columns:\n    - !Single\n      name:\n      - t\n      - a\n      target_id: 134\n      target_name: null\n    - !Single\n      name:\n      - b\n      - a\n      target_id: 120\n      target_name: a\n    inputs:\n    - id: 125\n      name: t\n      table:\n      - default_db\n      - _literal_125\n    - id: 120\n      name: b\n      table:\n      - default_db\n      - _literal_120\n- - 0:3243-3288\n  - columns:\n    - !Single\n      name:\n      - t\n      - a\n      target_id: 134\n      target_name: null\n    - !Single\n      name:\n      - b\n      - a\n      target_id: 120\n      target_name: a\n    inputs:\n    - id: 125\n      name: t\n      table:\n      - default_db\n      - _literal_125\n    - id: 120\n      name: b\n      table:\n      - default_db\n      - _literal_120\n- - 1:165-238\n  - columns:\n    - !Single\n      name:\n      - t\n      - a\n      target_id: 205\n      target_name: null\n    inputs:\n    - id: 125\n      name: t\n      table:\n      - default_db\n      - _literal_125\n    - id: 120\n      name: b\n      table:\n      - default_db\n      - _literal_120\n- - 1:239-245\n  - columns:\n    - !Single\n      name:\n      - t\n      - a\n      target_id: 205\n      target_name: null\n    inputs:\n    - id: 125\n      name: t\n      table:\n      - default_db\n      - _literal_125\n    - id: 120\n      name: b\n      table:\n      - default_db\n      - _literal_120\nnodes:\n- id: 120\n  kind: Array\n  span: 1:173-237\n  parent: 187\n- id: 125\n  kind: Array\n  span: 1:36-55\n  parent: 152\n- id: 134\n  kind: Ident\n  ident: !Ident\n  - this\n  - t\n  - a\n  targets:\n  - 125\n  parent: 136\n- id: 136\n  kind: Tuple\n  span: 1:64-69\n  children:\n  - 134\n- id: 152\n  kind: 'TransformCall: Take'\n  span: 1:71-77\n  children:\n  - 125\n  - 153\n  parent: 187\n- id: 153\n  kind: Literal\n  parent: 152\n- id: 176\n  kind: Ident\n  ident: !Ident\n  - this\n  - t\n  - a\n  targets:\n  - 134\n- id: 179\n  kind: Ident\n  ident: !Ident\n  - that\n  - b\n  - a\n  targets:\n  - 120\n- id: 185\n  kind: RqOperator\n  span: 0:3192-3239\n  targets:\n  - 176\n  - 179\n  parent: 187\n- id: 187\n  kind: 'TransformCall: Join'\n  span: 0:3163-3240\n  children:\n  - 152\n  - 120\n  - 185\n  parent: 203\n- id: 195\n  kind: Ident\n  span: 0:5981-5989\n  ident: !Ident\n  - this\n  - b\n  - a\n  targets:\n  - 120\n- id: 199\n  kind: RqOperator\n  span: 0:3251-3287\n  targets:\n  - 195\n  - 202\n  parent: 203\n- id: 202\n  kind: Literal\n  span: 0:5993-5997\n- id: 203\n  kind: 'TransformCall: Filter'\n  span: 0:3243-3288\n  children:\n  - 187\n  - 199\n  parent: 207\n- id: 205\n  kind: Ident\n  ident: !Ident\n  - this\n  - t\n  - a\n  targets:\n  - 134\n  parent: 206\n- id: 206\n  kind: Tuple\n  span: 0:3298-3301\n  children:\n  - 205\n  parent: 207\n- id: 207\n  kind: 'TransformCall: Select'\n  span: 1:165-238\n  children:\n  - 203\n  - 206\n  parent: 210\n- id: 208\n  kind: Ident\n  span: 1:244-245\n  ident: !Ident\n  - this\n  - t\n  - a\n  targets:\n  - 205\n  parent: 210\n- id: 210\n  kind: 'TransformCall: Sort'\n  span: 1:239-245\n  children:\n  - 207\n  - 208\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Let\n      name: distinct\n      value:\n        Func:\n          return_ty: null\n          body:\n            Pipeline:\n              exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                    - from\n                    span: 1:36-40\n                  args:\n                  - Ident:\n                    - _param\n                    - rel\n                    span: 1:45-55\n                    alias: t\n                span: 1:36-55\n              - FuncCall:\n                  name:\n                    Ident:\n                    - group\n                    span: 1:58-63\n                  args:\n                  - Tuple:\n                    - Ident:\n                      - t\n                      - '*'\n                      span: 1:65-68\n                    span: 1:64-69\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - take\n                        span: 1:71-75\n                      args:\n                      - Literal:\n                          Integer: 1\n                        span: 1:76-77\n                    span: 1:71-77\n                span: 1:58-78\n            span: 1:35-79\n          params:\n          - name: rel\n            default_value: null\n          named_params: []\n        span: 1:28-79\n    span: 1:0-79\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from_text\n                span: 1:81-90\n              args:\n              - Literal:\n                  String: '{ \"columns\": [\"a\"], \"data\": [[1], [2], [2], [3]] }'\n                span: 1:103-155\n              named_args:\n                format:\n                  Ident:\n                  - json\n                  span: 1:98-102\n            span: 1:81-155\n          - Ident:\n            - distinct\n            span: 1:156-164\n          - FuncCall:\n              name:\n                Ident:\n                - remove\n                span: 1:165-171\n              args:\n              - FuncCall:\n                  name:\n                    Ident:\n                    - from_text\n                    span: 1:173-182\n                  args:\n                  - Literal:\n                      String: '{ \"columns\": [\"a\"], \"data\": [[1], [2]] }'\n                    span: 1:195-237\n                  named_args:\n                    format:\n                      Ident:\n                      - json\n                      span: 1:190-194\n                span: 1:173-237\n            span: 1:165-238\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:239-243\n              args:\n              - Ident:\n                - a\n                span: 1:244-245\n            span: 1:239-245\n        span: 1:81-245\n    span: 1:79-245\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom e=employees\\nfilter first_name != \\\"Mitchell\\\"\\nsort {first_name, last_name}\\n\\n# joining may use HashMerge, which can undo ORDER BY\\njoin manager=employees side:left (e.reports_to == manager.employee_id)\\n\\nselect {e.first_name, e.last_name, manager.first_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort.prql\n---\nframes:\n- - 1:30-61\n  - columns:\n    - !All\n      input_id: 126\n      except: []\n    inputs:\n    - id: 126\n      name: e\n      table:\n      - default_db\n      - employees\n- - 1:62-90\n  - columns:\n    - !All\n      input_id: 126\n      except: []\n    inputs:\n    - id: 126\n      name: e\n      table:\n      - default_db\n      - employees\n- - 1:145-215\n  - columns:\n    - !All\n      input_id: 126\n      except: []\n    - !All\n      input_id: 117\n      except: []\n    inputs:\n    - id: 126\n      name: e\n      table:\n      - default_db\n      - employees\n    - id: 117\n      name: manager\n      table:\n      - default_db\n      - employees\n- - 1:217-271\n  - columns:\n    - !Single\n      name: null\n      target_id: 142\n      target_name: null\n    - !Single\n      name:\n      - e\n      - last_name\n      target_id: 143\n      target_name: null\n    - !Single\n      name:\n      - manager\n      - first_name\n      target_id: 144\n      target_name: null\n    inputs:\n    - id: 126\n      name: e\n      table:\n      - default_db\n      - employees\n    - id: 117\n      name: manager\n      table:\n      - default_db\n      - employees\nnodes:\n- id: 117\n  kind: Ident\n  span: 1:158-167\n  ident: !Ident\n  - default_db\n  - employees\n  parent: 141\n- id: 126\n  kind: Ident\n  span: 1:13-29\n  ident: !Ident\n  - default_db\n  - employees\n  parent: 132\n- id: 128\n  kind: RqOperator\n  span: 1:37-61\n  targets:\n  - 130\n  - 131\n  parent: 132\n- id: 130\n  kind: Ident\n  span: 1:37-47\n  ident: !Ident\n  - this\n  - e\n  - first_name\n  targets:\n  - 126\n- id: 131\n  kind: Literal\n  span: 1:51-61\n- id: 132\n  kind: 'TransformCall: Filter'\n  span: 1:30-61\n  children:\n  - 126\n  - 128\n  parent: 136\n- id: 133\n  kind: Ident\n  span: 1:68-78\n  ident: !Ident\n  - this\n  - e\n  - first_name\n  targets:\n  - 126\n  parent: 136\n- id: 134\n  kind: Ident\n  span: 1:80-89\n  ident: !Ident\n  - this\n  - e\n  - last_name\n  targets:\n  - 126\n  parent: 136\n- id: 136\n  kind: 'TransformCall: Sort'\n  span: 1:62-90\n  children:\n  - 132\n  - 133\n  - 134\n  parent: 141\n- id: 137\n  kind: RqOperator\n  span: 1:179-214\n  targets:\n  - 139\n  - 140\n  parent: 141\n- id: 139\n  kind: Ident\n  span: 1:179-191\n  ident: !Ident\n  - this\n  - e\n  - reports_to\n  targets:\n  - 126\n- id: 140\n  kind: Ident\n  span: 1:195-214\n  ident: !Ident\n  - that\n  - manager\n  - employee_id\n  targets:\n  - 117\n- id: 141\n  kind: 'TransformCall: Join'\n  span: 1:145-215\n  children:\n  - 136\n  - 117\n  - 137\n  parent: 146\n- id: 142\n  kind: Ident\n  span: 1:225-237\n  ident: !Ident\n  - this\n  - e\n  - first_name\n  targets:\n  - 126\n  parent: 145\n- id: 143\n  kind: Ident\n  span: 1:239-250\n  ident: !Ident\n  - this\n  - e\n  - last_name\n  targets:\n  - 126\n  parent: 145\n- id: 144\n  kind: Ident\n  span: 1:252-270\n  ident: !Ident\n  - this\n  - manager\n  - first_name\n  targets:\n  - 117\n  parent: 145\n- id: 145\n  kind: Tuple\n  span: 1:224-271\n  children:\n  - 142\n  - 143\n  - 144\n  parent: 146\n- id: 146\n  kind: 'TransformCall: Select'\n  span: 1:217-271\n  children:\n  - 141\n  - 145\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:13-17\n              args:\n              - Ident:\n                - employees\n                span: 1:20-29\n                alias: e\n            span: 1:13-29\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:30-36\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - first_name\n                    span: 1:37-47\n                  op: Ne\n                  right:\n                    Literal:\n                      String: Mitchell\n                    span: 1:51-61\n                span: 1:37-61\n            span: 1:30-61\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:62-66\n              args:\n              - Tuple:\n                - Ident:\n                  - first_name\n                  span: 1:68-78\n                - Ident:\n                  - last_name\n                  span: 1:80-89\n                span: 1:67-90\n            span: 1:62-90\n          - FuncCall:\n              name:\n                Ident:\n                - join\n                span: 1:145-149\n              args:\n              - Ident:\n                - employees\n                span: 1:158-167\n                alias: manager\n              - Binary:\n                  left:\n                    Ident:\n                    - e\n                    - reports_to\n                    span: 1:179-191\n                  op: Eq\n                  right:\n                    Ident:\n                    - manager\n                    - employee_id\n                    span: 1:195-214\n                span: 1:179-214\n              named_args:\n                side:\n                  Ident:\n                  - left\n                  span: 1:173-177\n            span: 1:145-215\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:217-223\n              args:\n              - Tuple:\n                - Ident:\n                  - e\n                  - first_name\n                  span: 1:225-237\n                - Ident:\n                  - e\n                  - last_name\n                  span: 1:239-250\n                - Ident:\n                  - manager\n                  - first_name\n                  span: 1:252-270\n                span: 1:224-271\n            span: 1:217-271\n        span: 1:13-271\n    span: 1:0-271\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__sort_2.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from albums\\nselect { AA=album_id, artist_id }\\nsort AA\\nfilter AA >= 25\\njoin artists (==artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_2.prql\n---\nframes:\n- - 1:12-45\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 128\n      target_name: null\n    - !Single\n      name:\n      - albums\n      - artist_id\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 126\n      name: albums\n      table:\n      - default_db\n      - albums\n- - 1:46-53\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 128\n      target_name: null\n    - !Single\n      name:\n      - albums\n      - artist_id\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 126\n      name: albums\n      table:\n      - default_db\n      - albums\n- - 1:54-69\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 128\n      target_name: null\n    - !Single\n      name:\n      - albums\n      - artist_id\n      target_id: 129\n      target_name: null\n    inputs:\n    - id: 126\n      name: albums\n      table:\n      - default_db\n      - albums\n- - 1:70-96\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 128\n      target_name: null\n    - !Single\n      name:\n      - albums\n      - artist_id\n      target_id: 129\n      target_name: null\n    - !All\n      input_id: 114\n      except: []\n    inputs:\n    - id: 126\n      name: albums\n      table:\n      - default_db\n      - albums\n    - id: 114\n      name: artists\n      table:\n      - default_db\n      - artists\nnodes:\n- id: 114\n  kind: Ident\n  span: 1:75-82\n  ident: !Ident\n  - default_db\n  - artists\n  parent: 144\n- id: 126\n  kind: Ident\n  span: 1:0-11\n  ident: !Ident\n  - default_db\n  - albums\n  parent: 131\n- id: 128\n  kind: Ident\n  span: 1:24-32\n  alias: AA\n  ident: !Ident\n  - this\n  - albums\n  - album_id\n  targets:\n  - 126\n  parent: 130\n- id: 129\n  kind: Ident\n  span: 1:34-43\n  ident: !Ident\n  - this\n  - albums\n  - artist_id\n  targets:\n  - 126\n  parent: 130\n- id: 130\n  kind: Tuple\n  span: 1:19-45\n  children:\n  - 128\n  - 129\n  parent: 131\n- id: 131\n  kind: 'TransformCall: Select'\n  span: 1:12-45\n  children:\n  - 126\n  - 130\n  parent: 134\n- id: 132\n  kind: Ident\n  span: 1:51-53\n  ident: !Ident\n  - this\n  - AA\n  targets:\n  - 128\n  parent: 134\n- id: 134\n  kind: 'TransformCall: Sort'\n  span: 1:46-53\n  children:\n  - 131\n  - 132\n  parent: 139\n- id: 135\n  kind: RqOperator\n  span: 1:61-69\n  targets:\n  - 137\n  - 138\n  parent: 139\n- id: 137\n  kind: Ident\n  span: 1:61-63\n  ident: !Ident\n  - this\n  - AA\n  targets:\n  - 128\n- id: 138\n  kind: Literal\n  span: 1:67-69\n- id: 139\n  kind: 'TransformCall: Filter'\n  span: 1:54-69\n  children:\n  - 134\n  - 135\n  parent: 144\n- id: 140\n  kind: RqOperator\n  span: 1:84-95\n  targets:\n  - 142\n  - 143\n  parent: 144\n- id: 142\n  kind: Ident\n  span: 1:86-95\n  ident: !Ident\n  - this\n  - albums\n  - artist_id\n  targets:\n  - 129\n- id: 143\n  kind: Ident\n  span: 1:86-95\n  ident: !Ident\n  - that\n  - artists\n  - artist_id\n  targets:\n  - 114\n- id: 144\n  kind: 'TransformCall: Join'\n  span: 1:70-96\n  children:\n  - 139\n  - 114\n  - 140\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:0-4\n              args:\n              - Ident:\n                - albums\n                span: 1:5-11\n            span: 1:0-11\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:12-18\n              args:\n              - Tuple:\n                - Ident:\n                  - album_id\n                  span: 1:24-32\n                  alias: AA\n                - Ident:\n                  - artist_id\n                  span: 1:34-43\n                span: 1:19-45\n            span: 1:12-45\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:46-50\n              args:\n              - Ident:\n                - AA\n                span: 1:51-53\n            span: 1:46-53\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:54-60\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - AA\n                    span: 1:61-63\n                  op: Gte\n                  right:\n                    Literal:\n                      Integer: 25\n                    span: 1:67-69\n                span: 1:61-69\n            span: 1:54-69\n          - FuncCall:\n              name:\n                Ident:\n                - join\n                span: 1:70-74\n              args:\n              - Ident:\n                - artists\n                span: 1:75-82\n              - Unary:\n                  op: EqSelf\n                  expr:\n                    Ident:\n                    - artist_id\n                    span: 1:86-95\n                span: 1:84-95\n            span: 1:70-96\n        span: 1:0-96\n    span: 1:0-96\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__sort_3.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from [{track_id=0, album_id=1, genre_id=2}]\\nselect { AA=track_id, album_id, genre_id }\\nsort AA\\njoin side:left [{album_id=1, album_title=\\\"Songs\\\"}] (==album_id)\\nselect { AA, AT = album_title ?? \\\"unknown\\\", genre_id }\\nfilter AA < 25\\njoin side:left [{genre_id=1, genre_title=\\\"Rock\\\"}] (==genre_id)\\nselect { AA, AT, GT = genre_title ?? \\\"unknown\\\" }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_3.prql\n---\nframes:\n- - 1:44-86\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 148\n      target_name: null\n    - !Single\n      name:\n      - _literal_142\n      - album_id\n      target_id: 149\n      target_name: null\n    - !Single\n      name:\n      - _literal_142\n      - genre_id\n      target_id: 150\n      target_name: null\n    inputs:\n    - id: 142\n      name: _literal_142\n      table:\n      - default_db\n      - _literal_142\n- - 1:87-94\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 148\n      target_name: null\n    - !Single\n      name:\n      - _literal_142\n      - album_id\n      target_id: 149\n      target_name: null\n    - !Single\n      name:\n      - _literal_142\n      - genre_id\n      target_id: 150\n      target_name: null\n    inputs:\n    - id: 142\n      name: _literal_142\n      table:\n      - default_db\n      - _literal_142\n- - 1:95-158\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 148\n      target_name: null\n    - !Single\n      name:\n      - _literal_142\n      - album_id\n      target_id: 149\n      target_name: null\n    - !Single\n      name:\n      - _literal_142\n      - genre_id\n      target_id: 150\n      target_name: null\n    - !Single\n      name:\n      - _literal_130\n      - album_id\n      target_id: 130\n      target_name: album_id\n    - !Single\n      name:\n      - _literal_130\n      - album_title\n      target_id: 130\n      target_name: album_title\n    inputs:\n    - id: 142\n      name: _literal_142\n      table:\n      - default_db\n      - _literal_142\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n- - 1:159-213\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 161\n      target_name: null\n    - !Single\n      name:\n      - AT\n      target_id: 162\n      target_name: null\n    - !Single\n      name:\n      - _literal_142\n      - genre_id\n      target_id: 166\n      target_name: null\n    inputs:\n    - id: 142\n      name: _literal_142\n      table:\n      - default_db\n      - _literal_142\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n- - 1:214-228\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 161\n      target_name: null\n    - !Single\n      name:\n      - AT\n      target_id: 162\n      target_name: null\n    - !Single\n      name:\n      - _literal_142\n      - genre_id\n      target_id: 166\n      target_name: null\n    inputs:\n    - id: 142\n      name: _literal_142\n      table:\n      - default_db\n      - _literal_142\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n- - 1:229-291\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 161\n      target_name: null\n    - !Single\n      name:\n      - AT\n      target_id: 162\n      target_name: null\n    - !Single\n      name:\n      - _literal_142\n      - genre_id\n      target_id: 166\n      target_name: null\n    - !Single\n      name:\n      - _literal_117\n      - genre_id\n      target_id: 117\n      target_name: genre_id\n    - !Single\n      name:\n      - _literal_117\n      - genre_title\n      target_id: 117\n      target_name: genre_title\n    inputs:\n    - id: 142\n      name: _literal_142\n      table:\n      - default_db\n      - _literal_142\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n    - id: 117\n      name: _literal_117\n      table:\n      - default_db\n      - _literal_117\n- - 1:292-340\n  - columns:\n    - !Single\n      name:\n      - AA\n      target_id: 179\n      target_name: null\n    - !Single\n      name:\n      - AT\n      target_id: 180\n      target_name: null\n    - !Single\n      name:\n      - GT\n      target_id: 181\n      target_name: null\n    inputs:\n    - id: 142\n      name: _literal_142\n      table:\n      - default_db\n      - _literal_142\n    - id: 130\n      name: _literal_130\n      table:\n      - default_db\n      - _literal_130\n    - id: 117\n      name: _literal_117\n      table:\n      - default_db\n      - _literal_117\nnodes:\n- id: 117\n  kind: Array\n  span: 1:244-278\n  children:\n  - 118\n  parent: 178\n- id: 118\n  kind: Tuple\n  span: 1:245-277\n  children:\n  - 119\n  - 120\n  parent: 117\n- id: 119\n  kind: Literal\n  span: 1:255-256\n  alias: genre_id\n  parent: 118\n- id: 120\n  kind: Literal\n  span: 1:270-276\n  alias: genre_title\n  parent: 118\n- id: 130\n  kind: Array\n  span: 1:110-145\n  children:\n  - 131\n  parent: 160\n- id: 131\n  kind: Tuple\n  span: 1:111-144\n  children:\n  - 132\n  - 133\n  parent: 130\n- id: 132\n  kind: Literal\n  span: 1:121-122\n  alias: album_id\n  parent: 131\n- id: 133\n  kind: Literal\n  span: 1:136-143\n  alias: album_title\n  parent: 131\n- id: 142\n  kind: Array\n  span: 1:0-43\n  children:\n  - 143\n  parent: 152\n- id: 143\n  kind: Tuple\n  span: 1:6-42\n  children:\n  - 144\n  - 145\n  - 146\n  parent: 142\n- id: 144\n  kind: Literal\n  span: 1:16-17\n  alias: track_id\n  parent: 143\n- id: 145\n  kind: Literal\n  span: 1:28-29\n  alias: album_id\n  parent: 143\n- id: 146\n  kind: Literal\n  span: 1:40-41\n  alias: genre_id\n  parent: 143\n- id: 148\n  kind: Ident\n  span: 1:56-64\n  alias: AA\n  ident: !Ident\n  - this\n  - _literal_142\n  - track_id\n  targets:\n  - 142\n  parent: 151\n- id: 149\n  kind: Ident\n  span: 1:66-74\n  ident: !Ident\n  - this\n  - _literal_142\n  - album_id\n  targets:\n  - 142\n  parent: 151\n- id: 150\n  kind: Ident\n  span: 1:76-84\n  ident: !Ident\n  - this\n  - _literal_142\n  - genre_id\n  targets:\n  - 142\n  parent: 151\n- id: 151\n  kind: Tuple\n  span: 1:51-86\n  children:\n  - 148\n  - 149\n  - 150\n  parent: 152\n- id: 152\n  kind: 'TransformCall: Select'\n  span: 1:44-86\n  children:\n  - 142\n  - 151\n  parent: 155\n- id: 153\n  kind: Ident\n  span: 1:92-94\n  ident: !Ident\n  - this\n  - AA\n  targets:\n  - 148\n  parent: 155\n- id: 155\n  kind: 'TransformCall: Sort'\n  span: 1:87-94\n  children:\n  - 152\n  - 153\n  parent: 160\n- id: 156\n  kind: RqOperator\n  span: 1:147-157\n  targets:\n  - 158\n  - 159\n  parent: 160\n- id: 158\n  kind: Ident\n  span: 1:149-157\n  ident: !Ident\n  - this\n  - _literal_142\n  - album_id\n  targets:\n  - 149\n- id: 159\n  kind: Ident\n  span: 1:149-157\n  ident: !Ident\n  - that\n  - _literal_130\n  - album_id\n  targets:\n  - 130\n- id: 160\n  kind: 'TransformCall: Join'\n  span: 1:95-158\n  children:\n  - 155\n  - 130\n  - 156\n  parent: 168\n- id: 161\n  kind: Ident\n  span: 1:168-170\n  ident: !Ident\n  - this\n  - AA\n  targets:\n  - 148\n  parent: 167\n- id: 162\n  kind: RqOperator\n  span: 1:177-201\n  alias: AT\n  targets:\n  - 164\n  - 165\n  parent: 167\n- id: 164\n  kind: Ident\n  span: 1:177-188\n  ident: !Ident\n  - this\n  - _literal_130\n  - album_title\n  targets:\n  - 130\n- id: 165\n  kind: Literal\n  span: 1:192-201\n- id: 166\n  kind: Ident\n  span: 1:203-211\n  ident: !Ident\n  - this\n  - _literal_142\n  - genre_id\n  targets:\n  - 150\n  parent: 167\n- id: 167\n  kind: Tuple\n  span: 1:166-213\n  children:\n  - 161\n  - 162\n  - 166\n  parent: 168\n- id: 168\n  kind: 'TransformCall: Select'\n  span: 1:159-213\n  children:\n  - 160\n  - 167\n  parent: 173\n- id: 169\n  kind: RqOperator\n  span: 1:221-228\n  targets:\n  - 171\n  - 172\n  parent: 173\n- id: 171\n  kind: Ident\n  span: 1:221-223\n  ident: !Ident\n  - this\n  - AA\n  targets:\n  - 161\n- id: 172\n  kind: Literal\n  span: 1:226-228\n- id: 173\n  kind: 'TransformCall: Filter'\n  span: 1:214-228\n  children:\n  - 168\n  - 169\n  parent: 178\n- id: 174\n  kind: RqOperator\n  span: 1:280-290\n  targets:\n  - 176\n  - 177\n  parent: 178\n- id: 176\n  kind: Ident\n  span: 1:282-290\n  ident: !Ident\n  - this\n  - _literal_142\n  - genre_id\n  targets:\n  - 166\n- id: 177\n  kind: Ident\n  span: 1:282-290\n  ident: !Ident\n  - that\n  - _literal_117\n  - genre_id\n  targets:\n  - 117\n- id: 178\n  kind: 'TransformCall: Join'\n  span: 1:229-291\n  children:\n  - 173\n  - 117\n  - 174\n  parent: 186\n- id: 179\n  kind: Ident\n  span: 1:301-303\n  ident: !Ident\n  - this\n  - AA\n  targets:\n  - 161\n  parent: 185\n- id: 180\n  kind: Ident\n  span: 1:305-307\n  ident: !Ident\n  - this\n  - AT\n  targets:\n  - 162\n  parent: 185\n- id: 181\n  kind: RqOperator\n  span: 1:314-338\n  alias: GT\n  targets:\n  - 183\n  - 184\n  parent: 185\n- id: 183\n  kind: Ident\n  span: 1:314-325\n  ident: !Ident\n  - this\n  - _literal_117\n  - genre_title\n  targets:\n  - 117\n- id: 184\n  kind: Literal\n  span: 1:329-338\n- id: 185\n  kind: Tuple\n  span: 1:299-340\n  children:\n  - 179\n  - 180\n  - 181\n  parent: 186\n- id: 186\n  kind: 'TransformCall: Select'\n  span: 1:292-340\n  children:\n  - 178\n  - 185\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:0-4\n              args:\n              - Array:\n                - Tuple:\n                  - Literal:\n                      Integer: 0\n                    span: 1:16-17\n                    alias: track_id\n                  - Literal:\n                      Integer: 1\n                    span: 1:28-29\n                    alias: album_id\n                  - Literal:\n                      Integer: 2\n                    span: 1:40-41\n                    alias: genre_id\n                  span: 1:6-42\n                span: 1:5-43\n            span: 1:0-43\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:44-50\n              args:\n              - Tuple:\n                - Ident:\n                  - track_id\n                  span: 1:56-64\n                  alias: AA\n                - Ident:\n                  - album_id\n                  span: 1:66-74\n                - Ident:\n                  - genre_id\n                  span: 1:76-84\n                span: 1:51-86\n            span: 1:44-86\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:87-91\n              args:\n              - Ident:\n                - AA\n                span: 1:92-94\n            span: 1:87-94\n          - FuncCall:\n              name:\n                Ident:\n                - join\n                span: 1:95-99\n              args:\n              - Array:\n                - Tuple:\n                  - Literal:\n                      Integer: 1\n                    span: 1:121-122\n                    alias: album_id\n                  - Literal:\n                      String: Songs\n                    span: 1:136-143\n                    alias: album_title\n                  span: 1:111-144\n                span: 1:110-145\n              - Unary:\n                  op: EqSelf\n                  expr:\n                    Ident:\n                    - album_id\n                    span: 1:149-157\n                span: 1:147-157\n              named_args:\n                side:\n                  Ident:\n                  - left\n                  span: 1:105-109\n            span: 1:95-158\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:159-165\n              args:\n              - Tuple:\n                - Ident:\n                  - AA\n                  span: 1:168-170\n                - Binary:\n                    left:\n                      Ident:\n                      - album_title\n                      span: 1:177-188\n                    op: Coalesce\n                    right:\n                      Literal:\n                        String: unknown\n                      span: 1:192-201\n                  span: 1:177-201\n                  alias: AT\n                - Ident:\n                  - genre_id\n                  span: 1:203-211\n                span: 1:166-213\n            span: 1:159-213\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:214-220\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - AA\n                    span: 1:221-223\n                  op: Lt\n                  right:\n                    Literal:\n                      Integer: 25\n                    span: 1:226-228\n                span: 1:221-228\n            span: 1:214-228\n          - FuncCall:\n              name:\n                Ident:\n                - join\n                span: 1:229-233\n              args:\n              - Array:\n                - Tuple:\n                  - Literal:\n                      Integer: 1\n                    span: 1:255-256\n                    alias: genre_id\n                  - Literal:\n                      String: Rock\n                    span: 1:270-276\n                    alias: genre_title\n                  span: 1:245-277\n                span: 1:244-278\n              - Unary:\n                  op: EqSelf\n                  expr:\n                    Ident:\n                    - genre_id\n                    span: 1:282-290\n                span: 1:280-290\n              named_args:\n                side:\n                  Ident:\n                  - left\n                  span: 1:239-243\n            span: 1:229-291\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:292-298\n              args:\n              - Tuple:\n                - Ident:\n                  - AA\n                  span: 1:301-303\n                - Ident:\n                  - AT\n                  span: 1:305-307\n                - Binary:\n                    left:\n                      Ident:\n                      - genre_title\n                      span: 1:314-325\n                    op: Coalesce\n                    right:\n                      Literal:\n                        String: unknown\n                      span: 1:329-338\n                  span: 1:314-338\n                  alias: GT\n                span: 1:299-340\n            span: 1:292-340\n        span: 1:0-340\n    span: 1:0-340\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__switch.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# glaredb:skip (May be a bag of String type conversion for Postgres Client)\\n# mssql:test\\nfrom tracks\\nsort milliseconds\\nselect display = case [\\n    composer != null => composer,\\n    genre_id < 17 => 'no composer',\\n    true => f'unknown composer'\\n]\\ntake 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/switch.prql\n---\nframes:\n- - 1:101-118\n  - columns:\n    - !All\n      input_id: 122\n      except: []\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:119-246\n  - columns:\n    - !Single\n      name:\n      - display\n      target_id: 127\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:247-254\n  - columns:\n    - !Single\n      name:\n      - display\n      target_id: 127\n      target_name: null\n    inputs:\n    - id: 122\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 122\n  kind: Ident\n  span: 1:89-100\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 126\n- id: 124\n  kind: Ident\n  span: 1:106-118\n  ident: !Ident\n  - this\n  - tracks\n  - milliseconds\n  targets:\n  - 122\n  parent: 126\n- id: 126\n  kind: 'TransformCall: Sort'\n  span: 1:101-118\n  children:\n  - 122\n  - 124\n  parent: 141\n- id: 127\n  kind: Case\n  span: 1:136-246\n  alias: display\n  targets:\n  - 128\n  - 132\n  - 133\n  - 137\n  - 138\n  - 139\n  parent: 140\n- id: 128\n  kind: RqOperator\n  span: 1:147-163\n  targets:\n  - 130\n  - 131\n- id: 130\n  kind: Ident\n  span: 1:147-155\n  ident: !Ident\n  - this\n  - tracks\n  - composer\n  targets:\n  - 122\n- id: 131\n  kind: Literal\n  span: 1:159-163\n- id: 132\n  kind: Ident\n  span: 1:167-175\n  ident: !Ident\n  - this\n  - tracks\n  - composer\n  targets:\n  - 122\n- id: 133\n  kind: RqOperator\n  span: 1:181-194\n  targets:\n  - 135\n  - 136\n- id: 135\n  kind: Ident\n  span: 1:181-189\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 122\n- id: 136\n  kind: Literal\n  span: 1:192-194\n- id: 137\n  kind: Literal\n  span: 1:198-211\n- id: 138\n  kind: Literal\n  span: 1:217-221\n- id: 139\n  kind: FString\n  span: 1:225-244\n- id: 140\n  kind: Tuple\n  span: 1:136-246\n  children:\n  - 127\n  parent: 141\n- id: 141\n  kind: 'TransformCall: Select'\n  span: 1:119-246\n  children:\n  - 126\n  - 140\n  parent: 143\n- id: 143\n  kind: 'TransformCall: Take'\n  span: 1:247-254\n  children:\n  - 141\n  - 144\n- id: 144\n  kind: Literal\n  parent: 143\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:89-93\n              args:\n              - Ident:\n                - tracks\n                span: 1:94-100\n            span: 1:89-100\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:101-105\n              args:\n              - Ident:\n                - milliseconds\n                span: 1:106-118\n            span: 1:101-118\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:119-125\n              args:\n              - Case:\n                - condition:\n                    Binary:\n                      left:\n                        Ident:\n                        - composer\n                        span: 1:147-155\n                      op: Ne\n                      right:\n                        Literal: 'Null'\n                        span: 1:159-163\n                    span: 1:147-163\n                  value:\n                    Ident:\n                    - composer\n                    span: 1:167-175\n                - condition:\n                    Binary:\n                      left:\n                        Ident:\n                        - genre_id\n                        span: 1:181-189\n                      op: Lt\n                      right:\n                        Literal:\n                          Integer: 17\n                        span: 1:192-194\n                    span: 1:181-194\n                  value:\n                    Literal:\n                      String: no composer\n                    span: 1:198-211\n                - condition:\n                    Literal:\n                      Boolean: true\n                    span: 1:217-221\n                  value:\n                    FString:\n                    - !String unknown composer\n                    span: 1:225-244\n                span: 1:136-246\n                alias: display\n            span: 1:119-246\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:247-251\n              args:\n              - Literal:\n                  Integer: 10\n                span: 1:252-254\n            span: 1:247-254\n        span: 1:89-254\n    span: 1:0-254\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {+track_id}\\ntake 3..5\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/take.prql\n---\nframes:\n- - 1:25-41\n  - columns:\n    - !All\n      input_id: 119\n      except: []\n    inputs:\n    - id: 119\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:42-51\n  - columns:\n    - !All\n      input_id: 119\n      except: []\n    inputs:\n    - id: 119\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 119\n  kind: Ident\n  span: 1:13-24\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 123\n- id: 121\n  kind: Ident\n  span: 1:31-40\n  ident: !Ident\n  - this\n  - tracks\n  - track_id\n  targets:\n  - 119\n  parent: 123\n- id: 123\n  kind: 'TransformCall: Sort'\n  span: 1:25-41\n  children:\n  - 119\n  - 121\n  parent: 127\n- id: 124\n  kind: Literal\n  span: 1:47-48\n  alias: start\n  parent: 127\n- id: 125\n  kind: Literal\n  span: 1:50-51\n  alias: end\n  parent: 127\n- id: 127\n  kind: 'TransformCall: Take'\n  span: 1:42-51\n  children:\n  - 123\n  - 124\n  - 125\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:13-17\n              args:\n              - Ident:\n                - tracks\n                span: 1:18-24\n            span: 1:13-24\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:25-29\n              args:\n              - Tuple:\n                - Unary:\n                    op: Add\n                    expr:\n                      Ident:\n                      - track_id\n                      span: 1:32-40\n                  span: 1:31-40\n                span: 1:30-41\n            span: 1:25-41\n          - FuncCall:\n              name:\n                Ident:\n                - take\n                span: 1:42-46\n              args:\n              - Range:\n                  start:\n                    Literal:\n                      Integer: 3\n                    span: 1:47-48\n                  end:\n                    Literal:\n                      Integer: 5\n                    span: 1:50-51\n                span: 1:47-51\n            span: 1:42-51\n        span: 1:13-51\n    span: 1:0-51\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__text_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# glaredb:skip — TODO: started raising an error on 2024-05-20; see `window.prql`\\n# for more details\\nfrom albums\\nselect {\\n    title,\\n    title_and_spaces = f\\\"  {title}  \\\",\\n    low = (title | text.lower),\\n    up = (title | text.upper),\\n    ltrimmed = (title | text.ltrim),\\n    rtrimmed = (title | text.rtrim),\\n    trimmed = (title | text.trim),\\n    len = (title | text.length),\\n    subs = (title | text.extract 2 5),\\n    replace = (title | text.replace \\\"al\\\" \\\"PIKA\\\"),\\n}\\nsort {title}\\nfilter (title | text.starts_with \\\"Black\\\") || (title | text.contains \\\"Sabbath\\\") || (title | text.ends_with \\\"os\\\")\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/text_module.prql\n---\nframes:\n- - 1:127-481\n  - columns:\n    - !Single\n      name:\n      - albums\n      - title\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - title_and_spaces\n      target_id: 125\n      target_name: null\n    - !Single\n      name:\n      - low\n      target_id: 127\n      target_name: null\n    - !Single\n      name:\n      - up\n      target_id: 130\n      target_name: null\n    - !Single\n      name:\n      - ltrimmed\n      target_id: 133\n      target_name: null\n    - !Single\n      name:\n      - rtrimmed\n      target_id: 136\n      target_name: null\n    - !Single\n      name:\n      - trimmed\n      target_id: 139\n      target_name: null\n    - !Single\n      name:\n      - len\n      target_id: 142\n      target_name: null\n    - !Single\n      name:\n      - subs\n      target_id: 145\n      target_name: null\n    - !Single\n      name:\n      - replace\n      target_id: 151\n      target_name: null\n    inputs:\n    - id: 122\n      name: albums\n      table:\n      - default_db\n      - albums\n- - 1:482-494\n  - columns:\n    - !Single\n      name:\n      - albums\n      - title\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - title_and_spaces\n      target_id: 125\n      target_name: null\n    - !Single\n      name:\n      - low\n      target_id: 127\n      target_name: null\n    - !Single\n      name:\n      - up\n      target_id: 130\n      target_name: null\n    - !Single\n      name:\n      - ltrimmed\n      target_id: 133\n      target_name: null\n    - !Single\n      name:\n      - rtrimmed\n      target_id: 136\n      target_name: null\n    - !Single\n      name:\n      - trimmed\n      target_id: 139\n      target_name: null\n    - !Single\n      name:\n      - len\n      target_id: 142\n      target_name: null\n    - !Single\n      name:\n      - subs\n      target_id: 145\n      target_name: null\n    - !Single\n      name:\n      - replace\n      target_id: 151\n      target_name: null\n    inputs:\n    - id: 122\n      name: albums\n      table:\n      - default_db\n      - albums\n- - 1:495-606\n  - columns:\n    - !Single\n      name:\n      - albums\n      - title\n      target_id: 124\n      target_name: null\n    - !Single\n      name:\n      - title_and_spaces\n      target_id: 125\n      target_name: null\n    - !Single\n      name:\n      - low\n      target_id: 127\n      target_name: null\n    - !Single\n      name:\n      - up\n      target_id: 130\n      target_name: null\n    - !Single\n      name:\n      - ltrimmed\n      target_id: 133\n      target_name: null\n    - !Single\n      name:\n      - rtrimmed\n      target_id: 136\n      target_name: null\n    - !Single\n      name:\n      - trimmed\n      target_id: 139\n      target_name: null\n    - !Single\n      name:\n      - len\n      target_id: 142\n      target_name: null\n    - !Single\n      name:\n      - subs\n      target_id: 145\n      target_name: null\n    - !Single\n      name:\n      - replace\n      target_id: 151\n      target_name: null\n    inputs:\n    - id: 122\n      name: albums\n      table:\n      - default_db\n      - albums\nnodes:\n- id: 122\n  kind: Ident\n  span: 1:115-126\n  ident: !Ident\n  - default_db\n  - albums\n  parent: 158\n- id: 124\n  kind: Ident\n  span: 1:140-145\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n  parent: 157\n- id: 125\n  kind: FString\n  span: 1:170-184\n  alias: title_and_spaces\n  targets:\n  - 126\n  parent: 157\n- id: 126\n  kind: Ident\n  span: 1:175-180\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n- id: 127\n  kind: RqOperator\n  span: 1:205-215\n  alias: low\n  targets:\n  - 129\n  parent: 157\n- id: 129\n  kind: Ident\n  span: 1:197-202\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n- id: 130\n  kind: RqOperator\n  span: 1:236-246\n  alias: up\n  targets:\n  - 132\n  parent: 157\n- id: 132\n  kind: Ident\n  span: 1:228-233\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n- id: 133\n  kind: RqOperator\n  span: 1:273-283\n  alias: ltrimmed\n  targets:\n  - 135\n  parent: 157\n- id: 135\n  kind: Ident\n  span: 1:265-270\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n- id: 136\n  kind: RqOperator\n  span: 1:310-320\n  alias: rtrimmed\n  targets:\n  - 138\n  parent: 157\n- id: 138\n  kind: Ident\n  span: 1:302-307\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n- id: 139\n  kind: RqOperator\n  span: 1:346-355\n  alias: trimmed\n  targets:\n  - 141\n  parent: 157\n- id: 141\n  kind: Ident\n  span: 1:338-343\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n- id: 142\n  kind: RqOperator\n  span: 1:377-388\n  alias: len\n  targets:\n  - 144\n  parent: 157\n- id: 144\n  kind: Ident\n  span: 1:369-374\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n- id: 145\n  kind: RqOperator\n  span: 1:411-427\n  alias: subs\n  targets:\n  - 148\n  - 149\n  - 150\n  parent: 157\n- id: 148\n  kind: Literal\n  span: 1:424-425\n- id: 149\n  kind: Literal\n  span: 1:426-427\n- id: 150\n  kind: Ident\n  span: 1:403-408\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n- id: 151\n  kind: RqOperator\n  span: 1:453-477\n  alias: replace\n  targets:\n  - 154\n  - 155\n  - 156\n  parent: 157\n- id: 154\n  kind: Literal\n  span: 1:466-470\n- id: 155\n  kind: Literal\n  span: 1:471-477\n- id: 156\n  kind: Ident\n  span: 1:445-450\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 122\n- id: 157\n  kind: Tuple\n  span: 1:134-481\n  children:\n  - 124\n  - 125\n  - 127\n  - 130\n  - 133\n  - 136\n  - 139\n  - 142\n  - 145\n  - 151\n  parent: 158\n- id: 158\n  kind: 'TransformCall: Select'\n  span: 1:127-481\n  children:\n  - 122\n  - 157\n  parent: 161\n- id: 159\n  kind: Ident\n  span: 1:488-493\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 124\n  parent: 161\n- id: 161\n  kind: 'TransformCall: Sort'\n  span: 1:482-494\n  children:\n  - 158\n  - 159\n  parent: 181\n- id: 162\n  kind: RqOperator\n  span: 1:502-606\n  targets:\n  - 164\n  - 176\n  parent: 181\n- id: 164\n  kind: RqOperator\n  span: 1:502-573\n  targets:\n  - 166\n  - 171\n- id: 166\n  kind: RqOperator\n  span: 1:511-535\n  targets:\n  - 169\n  - 170\n- id: 169\n  kind: Literal\n  span: 1:528-535\n- id: 170\n  kind: Ident\n  span: 1:503-508\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 124\n- id: 171\n  kind: RqOperator\n  span: 1:549-572\n  targets:\n  - 174\n  - 175\n- id: 174\n  kind: Literal\n  span: 1:563-572\n- id: 175\n  kind: Ident\n  span: 1:541-546\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 124\n- id: 176\n  kind: RqOperator\n  span: 1:586-605\n  targets:\n  - 179\n  - 180\n- id: 179\n  kind: Literal\n  span: 1:601-605\n- id: 180\n  kind: Ident\n  span: 1:578-583\n  ident: !Ident\n  - this\n  - albums\n  - title\n  targets:\n  - 124\n- id: 181\n  kind: 'TransformCall: Filter'\n  span: 1:495-606\n  children:\n  - 161\n  - 162\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:115-119\n              args:\n              - Ident:\n                - albums\n                span: 1:120-126\n            span: 1:115-126\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:127-133\n              args:\n              - Tuple:\n                - Ident:\n                  - title\n                  span: 1:140-145\n                - FString:\n                  - !String '  '\n                  - !Expr\n                    expr:\n                      Ident:\n                      - title\n                      span: 1:175-180\n                    format: null\n                  - !String '  '\n                  span: 1:170-184\n                  alias: title_and_spaces\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - title\n                      span: 1:197-202\n                    - Ident:\n                      - text\n                      - lower\n                      span: 1:205-215\n                  span: 1:196-216\n                  alias: low\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - title\n                      span: 1:228-233\n                    - Ident:\n                      - text\n                      - upper\n                      span: 1:236-246\n                  span: 1:227-247\n                  alias: up\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - title\n                      span: 1:265-270\n                    - Ident:\n                      - text\n                      - ltrim\n                      span: 1:273-283\n                  span: 1:264-284\n                  alias: ltrimmed\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - title\n                      span: 1:302-307\n                    - Ident:\n                      - text\n                      - rtrim\n                      span: 1:310-320\n                  span: 1:301-321\n                  alias: rtrimmed\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - title\n                      span: 1:338-343\n                    - Ident:\n                      - text\n                      - trim\n                      span: 1:346-355\n                  span: 1:337-356\n                  alias: trimmed\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - title\n                      span: 1:369-374\n                    - Ident:\n                      - text\n                      - length\n                      span: 1:377-388\n                  span: 1:368-389\n                  alias: len\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - title\n                      span: 1:403-408\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - text\n                          - extract\n                          span: 1:411-423\n                        args:\n                        - Literal:\n                            Integer: 2\n                          span: 1:424-425\n                        - Literal:\n                            Integer: 5\n                          span: 1:426-427\n                      span: 1:411-427\n                  span: 1:402-428\n                  alias: subs\n                - Pipeline:\n                    exprs:\n                    - Ident:\n                      - title\n                      span: 1:445-450\n                    - FuncCall:\n                        name:\n                          Ident:\n                          - text\n                          - replace\n                          span: 1:453-465\n                        args:\n                        - Literal:\n                            String: al\n                          span: 1:466-470\n                        - Literal:\n                            String: PIKA\n                          span: 1:471-477\n                      span: 1:453-477\n                  span: 1:444-478\n                  alias: replace\n                span: 1:134-481\n            span: 1:127-481\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:482-486\n              args:\n              - Tuple:\n                - Ident:\n                  - title\n                  span: 1:488-493\n                span: 1:487-494\n            span: 1:482-494\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:495-501\n              args:\n              - Binary:\n                  left:\n                    Binary:\n                      left:\n                        Pipeline:\n                          exprs:\n                          - Ident:\n                            - title\n                            span: 1:503-508\n                          - FuncCall:\n                              name:\n                                Ident:\n                                - text\n                                - starts_with\n                                span: 1:511-527\n                              args:\n                              - Literal:\n                                  String: Black\n                                span: 1:528-535\n                            span: 1:511-535\n                        span: 1:503-535\n                      op: Or\n                      right:\n                        Pipeline:\n                          exprs:\n                          - Ident:\n                            - title\n                            span: 1:541-546\n                          - FuncCall:\n                              name:\n                                Ident:\n                                - text\n                                - contains\n                                span: 1:549-562\n                              args:\n                              - Literal:\n                                  String: Sabbath\n                                span: 1:563-572\n                            span: 1:549-572\n                        span: 1:541-572\n                    span: 1:502-573\n                  op: Or\n                  right:\n                    Pipeline:\n                      exprs:\n                      - Ident:\n                        - title\n                        span: 1:578-583\n                      - FuncCall:\n                          name:\n                            Ident:\n                            - text\n                            - ends_with\n                            span: 1:586-600\n                          args:\n                          - Literal:\n                              String: os\n                            span: 1:601-605\n                        span: 1:586-605\n                    span: 1:578-605\n                span: 1:502-606\n            span: 1:495-606\n        span: 1:115-606\n    span: 1:0-606\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__window.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip problems with DISTINCT ON\\n# glaredb:skip — TODO: started raising an error on 2024-05-20, from https://github.com/PRQL/prql/actions/runs/9154902656/job/25198160283:\\n    # ERROR: This feature is not implemented: Unsupported ast node in sqltorel:\\n    # Substring { expr: Identifier(Ident { value: \\\"title\\\", quote_style: None }),\\n    # substring_from: Some(Value(Number(\\\"2\\\", false))), substring_for:\\n    # Some(Value(Number(\\\"5\\\", false))), special: true }\\nfrom tracks\\ngroup genre_id (\\n  sort milliseconds\\n  derive {\\n    num = row_number this,\\n    total = count this,\\n    last_val = last track_id,\\n  }\\n  take 10\\n)\\nsort {genre_id, milliseconds}\\nselect {track_id, genre_id, num, total, last_val}\\nfilter genre_id >= 22\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/window.prql\n---\nframes:\n- - 1:521-614\n  - columns:\n    - !All\n      input_id: 125\n      except:\n      - genre_id\n    - !Single\n      name:\n      - num\n      target_id: 163\n      target_name: null\n    - !Single\n      name:\n      - total\n      target_id: 171\n      target_name: null\n    - !Single\n      name:\n      - last_val\n      target_id: 173\n      target_name: null\n    inputs:\n    - id: 125\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:617-624\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 127\n      target_name: null\n    - !All\n      input_id: 125\n      except:\n      - genre_id\n    - !Single\n      name:\n      - num\n      target_id: 163\n      target_name: null\n    - !Single\n      name:\n      - total\n      target_id: 171\n      target_name: null\n    - !Single\n      name:\n      - last_val\n      target_id: 173\n      target_name: null\n    inputs:\n    - id: 125\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:627-656\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 127\n      target_name: null\n    - !All\n      input_id: 125\n      except:\n      - genre_id\n    - !Single\n      name:\n      - num\n      target_id: 163\n      target_name: null\n    - !Single\n      name:\n      - total\n      target_id: 171\n      target_name: null\n    - !Single\n      name:\n      - last_val\n      target_id: 173\n      target_name: null\n    inputs:\n    - id: 125\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:657-706\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - track_id\n      target_id: 187\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 188\n      target_name: null\n    - !Single\n      name:\n      - num\n      target_id: 189\n      target_name: null\n    - !Single\n      name:\n      - total\n      target_id: 190\n      target_name: null\n    - !Single\n      name:\n      - last_val\n      target_id: 191\n      target_name: null\n    inputs:\n    - id: 125\n      name: tracks\n      table:\n      - default_db\n      - tracks\n- - 1:707-728\n  - columns:\n    - !Single\n      name:\n      - tracks\n      - track_id\n      target_id: 187\n      target_name: null\n    - !Single\n      name:\n      - tracks\n      - genre_id\n      target_id: 188\n      target_name: null\n    - !Single\n      name:\n      - num\n      target_id: 189\n      target_name: null\n    - !Single\n      name:\n      - total\n      target_id: 190\n      target_name: null\n    - !Single\n      name:\n      - last_val\n      target_id: 191\n      target_name: null\n    inputs:\n    - id: 125\n      name: tracks\n      table:\n      - default_db\n      - tracks\nnodes:\n- id: 125\n  kind: Ident\n  span: 1:470-481\n  ident: !Ident\n  - default_db\n  - tracks\n  parent: 177\n- id: 127\n  kind: Ident\n  span: 1:488-496\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 125\n  parent: 136\n- id: 136\n  kind: Tuple\n  span: 1:488-496\n  children:\n  - 127\n- id: 155\n  kind: Ident\n  span: 1:506-518\n  ident: !Ident\n  - this\n  - tracks\n  - milliseconds\n  targets:\n  - 125\n- id: 163\n  kind: RqOperator\n  span: 1:540-555\n  alias: num\n  targets:\n  - 164\n  parent: 176\n- id: 164\n  kind: Literal\n- id: 171\n  kind: RqOperator\n  span: 1:569-579\n  alias: total\n  targets:\n  - 172\n  parent: 176\n- id: 172\n  kind: Literal\n- id: 173\n  kind: RqOperator\n  span: 1:596-609\n  alias: last_val\n  targets:\n  - 175\n  parent: 176\n- id: 175\n  kind: Ident\n  span: 1:601-609\n  ident: !Ident\n  - this\n  - tracks\n  - track_id\n  targets:\n  - 125\n- id: 176\n  kind: Tuple\n  span: 1:528-614\n  children:\n  - 163\n  - 171\n  - 173\n  parent: 177\n- id: 177\n  kind: 'TransformCall: Derive'\n  span: 1:521-614\n  children:\n  - 125\n  - 176\n  parent: 179\n- id: 179\n  kind: 'TransformCall: Take'\n  span: 1:617-624\n  children:\n  - 177\n  - 180\n  parent: 186\n- id: 180\n  kind: Literal\n  parent: 179\n- id: 183\n  kind: Ident\n  span: 1:633-641\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 127\n  parent: 186\n- id: 184\n  kind: Ident\n  span: 1:643-655\n  ident: !Ident\n  - this\n  - tracks\n  - milliseconds\n  targets:\n  - 125\n  parent: 186\n- id: 186\n  kind: 'TransformCall: Sort'\n  span: 1:627-656\n  children:\n  - 179\n  - 183\n  - 184\n  parent: 193\n- id: 187\n  kind: Ident\n  span: 1:665-673\n  ident: !Ident\n  - this\n  - tracks\n  - track_id\n  targets:\n  - 125\n  parent: 192\n- id: 188\n  kind: Ident\n  span: 1:675-683\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 127\n  parent: 192\n- id: 189\n  kind: Ident\n  span: 1:685-688\n  ident: !Ident\n  - this\n  - num\n  targets:\n  - 163\n  parent: 192\n- id: 190\n  kind: Ident\n  span: 1:690-695\n  ident: !Ident\n  - this\n  - total\n  targets:\n  - 171\n  parent: 192\n- id: 191\n  kind: Ident\n  span: 1:697-705\n  ident: !Ident\n  - this\n  - last_val\n  targets:\n  - 173\n  parent: 192\n- id: 192\n  kind: Tuple\n  span: 1:664-706\n  children:\n  - 187\n  - 188\n  - 189\n  - 190\n  - 191\n  parent: 193\n- id: 193\n  kind: 'TransformCall: Select'\n  span: 1:657-706\n  children:\n  - 186\n  - 192\n  parent: 198\n- id: 194\n  kind: RqOperator\n  span: 1:714-728\n  targets:\n  - 196\n  - 197\n  parent: 198\n- id: 196\n  kind: Ident\n  span: 1:714-722\n  ident: !Ident\n  - this\n  - tracks\n  - genre_id\n  targets:\n  - 188\n- id: 197\n  kind: Literal\n  span: 1:726-728\n- id: 198\n  kind: 'TransformCall: Filter'\n  span: 1:707-728\n  children:\n  - 193\n  - 194\nast:\n  name: Project\n  stmts:\n  - VarDef:\n      kind: Main\n      name: main\n      value:\n        Pipeline:\n          exprs:\n          - FuncCall:\n              name:\n                Ident:\n                - from\n                span: 1:470-474\n              args:\n              - Ident:\n                - tracks\n                span: 1:475-481\n            span: 1:470-481\n          - FuncCall:\n              name:\n                Ident:\n                - group\n                span: 1:482-487\n              args:\n              - Ident:\n                - genre_id\n                span: 1:488-496\n              - Pipeline:\n                  exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - sort\n                        span: 1:501-505\n                      args:\n                      - Ident:\n                        - milliseconds\n                        span: 1:506-518\n                    span: 1:501-518\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - derive\n                        span: 1:521-527\n                      args:\n                      - Tuple:\n                        - FuncCall:\n                            name:\n                              Ident:\n                              - row_number\n                              span: 1:540-550\n                            args:\n                            - Ident:\n                              - this\n                              span: 1:551-555\n                          span: 1:540-555\n                          alias: num\n                        - FuncCall:\n                            name:\n                              Ident:\n                              - count\n                              span: 1:569-574\n                            args:\n                            - Ident:\n                              - this\n                              span: 1:575-579\n                          span: 1:569-579\n                          alias: total\n                        - FuncCall:\n                            name:\n                              Ident:\n                              - last\n                              span: 1:596-600\n                            args:\n                            - Ident:\n                              - track_id\n                              span: 1:601-609\n                          span: 1:596-609\n                          alias: last_val\n                        span: 1:528-614\n                    span: 1:521-614\n                  - FuncCall:\n                      name:\n                        Ident:\n                        - take\n                        span: 1:617-621\n                      args:\n                      - Literal:\n                          Integer: 10\n                        span: 1:622-624\n                    span: 1:617-624\n                span: 1:501-624\n            span: 1:482-626\n          - FuncCall:\n              name:\n                Ident:\n                - sort\n                span: 1:627-631\n              args:\n              - Tuple:\n                - Ident:\n                  - genre_id\n                  span: 1:633-641\n                - Ident:\n                  - milliseconds\n                  span: 1:643-655\n                span: 1:632-656\n            span: 1:627-656\n          - FuncCall:\n              name:\n                Ident:\n                - select\n                span: 1:657-663\n              args:\n              - Tuple:\n                - Ident:\n                  - track_id\n                  span: 1:665-673\n                - Ident:\n                  - genre_id\n                  span: 1:675-683\n                - Ident:\n                  - num\n                  span: 1:685-688\n                - Ident:\n                  - total\n                  span: 1:690-695\n                - Ident:\n                  - last_val\n                  span: 1:697-705\n                span: 1:664-706\n            span: 1:657-706\n          - FuncCall:\n              name:\n                Ident:\n                - filter\n                span: 1:707-713\n              args:\n              - Binary:\n                  left:\n                    Ident:\n                    - genre_id\n                    span: 1:714-722\n                  op: Gte\n                  right:\n                    Literal:\n                      Integer: 22\n                    span: 1:726-728\n                span: 1:714-728\n            span: 1:707-728\n        span: 1:470-728\n    span: 1:0-728\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__aggregation.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mysql:skip\\n# clickhouse:skip\\n# glaredb:skip (the string_agg function is not supported)\\nfrom tracks\\nfilter genre_id == 100\\nderive empty_name = name == ''\\naggregate {sum track_id, concat_array name, all empty_name, any empty_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/aggregation.prql\n---\nfrom tracks\nfilter genre_id == 100\nderive empty_name = name == \"\"\naggregate {\n  sum track_id,\n  concat_array name,\n  all empty_name,\n  any empty_name,\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__append_select.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 10..15\\nappend (\\n  from invoices\\n  select { customer_id, invoice_id, billing_country }\\n  take 40..45\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select.prql\n---\nfrom invoices\nselect {customer_id, invoice_id, billing_country}\ntake 10..15\nappend (\n  from invoices\n  select {customer_id, invoice_id, billing_country}\n  take 40..45\n)\nselect {billing_country, invoice_id}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__append_select_compute.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nderive total = case [total < 10 => total * 2, true => total]\\nselect { customer_id, invoice_id, total }\\ntake 5\\nappend (\\n  from invoice_items\\n  derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\\n  select { invoice_line_id, invoice_id, unit_price }\\n  take 5\\n)\\nselect { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_compute.prql\n---\nfrom invoices\nderive total = case [\n  total < 10 => total * 2,\n  true => total,\n]\nselect {customer_id, invoice_id, total}\ntake 5\nappend (\n  from invoice_items\n  derive unit_price = case [\n    unit_price < 1 => unit_price * 2,\n    true => unit_price,\n  ]\n  select {invoice_line_id, invoice_id, unit_price}\n  take 5\n)\nselect {\n  a = customer_id * 2,\n  b = math.round 1 invoice_id * total,\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__append_select_multiple_with_null.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 5\\nappend (\\n  from employees\\n  select { employee_id, employee_id, country }\\n  take 5\\n)\\nappend (\\n  from invoice_items\\n  select { invoice_line_id, invoice_id, null }\\n  take 5\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql\n---\nfrom invoices\nselect {customer_id, invoice_id, billing_country}\ntake 5\nappend (\n  from employees\n  select {employee_id, employee_id, country}\n  take 5\n)\nappend (\n  from invoice_items\n  select {invoice_line_id, invoice_id, null}\n  take 5\n)\nselect {billing_country, invoice_id}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__append_select_nulls.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# duckdb:skip\\n# postgres:skip\\n\\nfrom invoices\\nselect {an_id = invoice_id, name = null}\\ntake 2\\nappend (\\n  from employees\\n  select {an_id = null, name = first_name}\\n  take 2\\n)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_nulls.prql\n---\nfrom invoices\nselect {an_id = invoice_id, name = null}\ntake 2\nappend (\n  from employees\n  select {an_id = null, name = first_name}\n  take 2\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__append_select_simple.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { invoice_id, billing_country }\\nappend (\\n  from invoices\\n  select { invoice_id = `invoice_id` + 100, billing_country }\\n)\\nfilter (billing_country | text.starts_with(\\\"I\\\"))\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_simple.prql\n---\nfrom invoices\nselect {invoice_id, billing_country}\nappend (\n  from invoices\n  select {\n    invoice_id = invoice_id + 100,\n    billing_country,\n  }\n)\nfilter (billing_country | text.starts_with \"I\")\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__arithmetic.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom [\\n    { id = 1, x_int =  13, x_float =  13.0, k_int =  5, k_float =  5.0 },\\n    { id = 2, x_int = -13, x_float = -13.0, k_int =  5, k_float =  5.0 },\\n    { id = 3, x_int =  13, x_float =  13.0, k_int = -5, k_float = -5.0 },\\n    { id = 4, x_int = -13, x_float = -13.0, k_int = -5, k_float = -5.0 },\\n]\\nselect {\\n    id,\\n\\n    x_int / k_int,\\n    x_int / k_float,\\n    x_float / k_int,\\n    x_float / k_float,\\n\\n    q_ii = x_int // k_int,\\n    q_if = x_int // k_float,\\n    q_fi = x_float // k_int,\\n    q_ff = x_float // k_float,\\n\\n    r_ii = x_int % k_int,\\n    r_if = x_int % k_float,\\n    r_fi = x_float % k_int,\\n    r_ff = x_float % k_float,\\n\\n    (q_ii * k_int + r_ii | math.round 0),\\n    (q_if * k_float + r_if | math.round 0),\\n    (q_fi * k_int + r_fi | math.round 0),\\n    (q_ff * k_float + r_ff | math.round 0),\\n}\\nsort id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/arithmetic.prql\n---\nfrom [\n  {\n    id = 1,\n    x_int = 13,\n    x_float = 13,\n    k_int = 5,\n    k_float = 5,\n  },\n  {\n    id = 2,\n    x_int = -13,\n    x_float = -13,\n    k_int = 5,\n    k_float = 5,\n  },\n  {\n    id = 3,\n    x_int = 13,\n    x_float = 13,\n    k_int = -5,\n    k_float = -5,\n  },\n  {\n    id = 4,\n    x_int = -13,\n    x_float = -13,\n    k_int = -5,\n    k_float = -5,\n  },\n]\nselect {\n  id,\n  x_int / k_int,\n  x_int / k_float,\n  x_float / k_int,\n  x_float / k_float,\n  q_ii = x_int // k_int,\n  q_if = x_int // k_float,\n  q_fi = x_float // k_int,\n  q_ff = x_float // k_float,\n  r_ii = x_int % k_int,\n  r_if = x_int % k_float,\n  r_fi = x_float % k_int,\n  r_ff = x_float % k_float,\n  (q_ii * k_int + r_ii | math.round 0),\n  (q_if * k_float + r_if | math.round 0),\n  (q_fi * k_int + r_fi | math.round 0),\n  (q_ff * k_float + r_ff | math.round 0),\n}\nsort id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__cast.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {-bytes}\\nselect {\\n    name,\\n    bin = ((album_id | as REAL) * 99)\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/cast.prql\n---\nfrom tracks\nsort {-bytes}\nselect {name, bin = (album_id | as REAL) * 99}\ntake 20\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__constants_only.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from genres\\ntake 10\\nfilter true\\ntake 20\\nfilter true\\nselect d = 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/constants_only.prql\n---\nfrom genres\ntake 10\nfilter true\ntake 20\nfilter true\nselect d = 10\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__date_to_text.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# generic:skip\\n# glaredb:skip\\n# sqlite:skip\\n# mssql:test\\nfrom invoices\\ntake 20\\nselect {\\n    d1 = (invoice_date | date.to_text \\\"%Y/%m/%d\\\"),\\n    d2 = (invoice_date | date.to_text \\\"%F\\\"),\\n    d3 = (invoice_date | date.to_text \\\"%D\\\"),\\n    d4 = (invoice_date | date.to_text \\\"%H:%M:%S.%f\\\"),\\n    d5 = (invoice_date | date.to_text \\\"%r\\\"),\\n    d6 = (invoice_date | date.to_text \\\"%A %B %-d %Y\\\"),\\n    d7 = (invoice_date | date.to_text \\\"%a, %-d %b %Y at %I:%M:%S %p\\\"),\\n    d8 = (invoice_date | date.to_text \\\"%+\\\"),\\n    d9 = (invoice_date | date.to_text \\\"%-d/%-m/%y\\\"),\\n    d10 = (invoice_date | date.to_text \\\"%-Hh %Mmin\\\"),\\n    d11 = (invoice_date | date.to_text \\\"%M'%S\\\\\\\"\\\"),\\n    d12 = (invoice_date | date.to_text \\\"100%% in %d days\\\"),\\n}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/date_to_text.prql\n---\nfrom invoices\ntake 20\nselect {\n  d1 = (invoice_date | date.to_text \"%Y/%m/%d\"),\n  d2 = (invoice_date | date.to_text \"%F\"),\n  d3 = (invoice_date | date.to_text \"%D\"),\n  d4 = (invoice_date | date.to_text \"%H:%M:%S.%f\"),\n  d5 = (invoice_date | date.to_text \"%r\"),\n  d6 = (invoice_date | date.to_text \"%A %B %-d %Y\"),\n  d7 = (invoice_date | date.to_text \"%a, %-d %b %Y at %I:%M:%S %p\"),\n  d8 = (invoice_date | date.to_text \"%+\"),\n  d9 = (invoice_date | date.to_text \"%-d/%-m/%y\"),\n  d10 = (invoice_date | date.to_text \"%-Hh %Mmin\"),\n  d11 = (invoice_date | date.to_text '''%M'%S\"'''),\n  d12 = (invoice_date | date.to_text \"100%% in %d days\"),\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__distinct.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {album_id, genre_id}\\ngroup tracks.* (take 1)\\nsort tracks.*\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct.prql\n---\nfrom tracks\nselect {album_id, genre_id}\ngroup tracks.`*` (take 1)\nsort tracks.`*`\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__distinct_on.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {genre_id, media_type_id, album_id}\\ngroup {genre_id, media_type_id} (sort {-album_id} | take 1)\\nsort {-genre_id, media_type_id}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct_on.prql\n---\nfrom tracks\nselect {genre_id, media_type_id, album_id}\ngroup {genre_id, media_type_id} (\n  sort {-album_id}\n  take 1\n)\nsort {-genre_id, media_type_id}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__genre_counts.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (ClickHouse prefers aliases to column names https://github.com/PRQL/prql/issues/2827)\\n# mssql:test\\nlet genre_count = (\\n    from genres\\n    aggregate {a = count name}\\n)\\n\\nfrom genre_count\\nfilter a > 0\\nselect a = -a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/genre_counts.prql\n---\nlet genre_count = (\n  from genres\n  aggregate {a = count name}\n)\n\nfrom genre_count\nfilter a > 0\nselect a = -a\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__group_all.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom a=albums\\ntake 10\\njoin tracks (==album_id)\\ngroup {a.album_id, a.title} (aggregate price = (sum tracks.unit_price | math.round 2))\\nsort album_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_all.prql\n---\nfrom a = albums\ntake 10\njoin tracks (==album_id)\ngroup {a.album_id, a.title} (aggregate price = (\n  sum tracks.unit_price\n  math.round 2\n))\nsort album_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__group_sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nderive d = album_id + 1\\ngroup d (\\n    aggregate {\\n        n1 = (track_id | sum),\\n    }\\n)\\nsort d\\ntake 10\\nselect { d1 = d, n1 }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort.prql\n---\nfrom tracks\nderive d = album_id + 1\ngroup d (aggregate {n1 = (track_id | sum)})\nsort d\ntake 10\nselect {d1 = d, n1}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__group_sort_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_derive_select_join.prql\n---\ns\"SELECT album_id,title,artist_id FROM albums\"\ngroup {artist_id} (aggregate {album_title_count = count this.title})\nsort {this.artist_id, this.album_title_count}\nderive {new_album_count = this.album_title_count}\nselect {this.artist_id, this.new_album_count}\njoin side:left s\"SELECT artist_id,name as artist_name FROM artists\" this.artist_id == that.artist_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__group_sort_filter_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nfilter (this.album_title_count) > 10\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_filter_derive_select_join.prql\n---\ns\"SELECT album_id,title,artist_id FROM albums\"\ngroup {artist_id} (aggregate {album_title_count = count this.title})\nsort {this.artist_id, this.album_title_count}\nfilter this.album_title_count > 10\nderive {new_album_count = this.album_title_count}\nselect {this.artist_id, this.new_album_count}\njoin side:left s\"SELECT artist_id,name as artist_name FROM artists\" this.artist_id == that.artist_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__group_sort_limit_take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# Compute the 3 longest songs for each genre and sort by genre\\n# mssql:test\\nfrom tracks\\nselect {genre_id,milliseconds}\\ngroup {genre_id} (\\n  sort {-milliseconds}\\n  take 3\\n)\\njoin genres (==genre_id)\\nselect {name, milliseconds}\\nsort {+name,-milliseconds}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_limit_take.prql\n---\nfrom tracks\nselect {genre_id, milliseconds}\ngroup {genre_id} (sort {-milliseconds} | take 3)\njoin genres (==genre_id)\nselect {name, milliseconds}\nsort {+name, -milliseconds}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__invoice_totals.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (clickhouse doesn't have lag function)\\n\\n#! Calculate a number of metrics about the sales of tracks in each city.\\nfrom i=invoices\\njoin ii=invoice_items (==invoice_id)\\nderive {\\n    city = i.billing_city,\\n    street = i.billing_address,\\n}\\ngroup {city, street} (\\n    derive total = ii.unit_price * ii.quantity\\n    aggregate {\\n        num_orders = count_distinct i.invoice_id,\\n        num_tracks = sum ii.quantity,\\n        total_price = sum total,\\n    }\\n)\\ngroup {city} (\\n    sort street\\n    window expanding:true (\\n        derive {running_total_num_tracks = sum num_tracks}\\n    )\\n)\\nsort {city, street}\\nderive {num_tracks_last_week = lag 7 num_tracks}\\nselect {\\n    city,\\n    street,\\n    num_orders,\\n    num_tracks,\\n    running_total_num_tracks,\\n    num_tracks_last_week\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/invoice_totals.prql\n---\nfrom i = invoices\njoin ii = invoice_items (==invoice_id)\nderive {\n  city = i.billing_city,\n  street = i.billing_address,\n}\ngroup {city, street} (\n  derive total = ii.unit_price * ii.quantity\n  aggregate {\n    num_orders = count_distinct i.invoice_id,\n    num_tracks = sum ii.quantity,\n    total_price = sum total,\n  }\n)\ngroup {city} (\n  sort street\n  window expanding:true (derive {\n    running_total_num_tracks = sum num_tracks,\n  })\n)\nsort {city, street}\nderive {num_tracks_last_week = lag 7 num_tracks}\nselect {\n  city,\n  street,\n  num_orders,\n  num_tracks,\n  running_total_num_tracks,\n  num_tracks_last_week,\n}\ntake 20\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__loop_01.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (DB::Exception: Syntax error)\\n# glaredb:skip (DataFusion does not support recursive CTEs https://github.com/apache/arrow-datafusion/issues/462)\\nfrom [{n = 1}]\\nselect n = n - 2\\nloop (filter n < 4 | select n = n + 1)\\nselect n = n * 2\\nsort n\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/loop_01.prql\n---\nfrom [{n = 1}]\nselect n = n - 2\nloop (filter n < 4 | select n = n + 1)\nselect n = n * 2\nsort n\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__math_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# sqlite:skip (see https://github.com/rusqlite/rusqlite/issues/1211)\\nfrom invoices\\ntake 5\\nselect {\\n    total_original = (total | math.round 2),\\n    total_x = (math.pi - total | math.round 2 | math.abs),\\n    total_floor = (math.floor total),\\n    total_ceil = (math.ceil total),\\n    total_log10 = (math.log10 total | math.round 3),\\n    total_log2 = (math.log 2 total | math.round 3),\\n    total_sqrt = (math.sqrt total | math.round 3),\\n    total_ln = (math.ln total | math.exp | math.round 2),\\n    total_cos = (math.cos total | math.acos | math.round 2),\\n    total_sin = (math.sin total | math.asin | math.round 2),\\n    total_tan = (math.tan total | math.atan | math.round 2),\\n    total_deg = (total | math.degrees | math.radians | math.round 2),\\n    total_square = (total | math.pow 2 | math.round 2),\\n    total_square_op = ((total ** 2) | math.round 2),\\n}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/math_module.prql\n---\nfrom invoices\ntake 5\nselect {\n  total_original = (total | math.round 2),\n  total_x = (\n    math.pi - total\n    math.round 2\n    math.abs\n  ),\n  total_floor = math.floor total,\n  total_ceil = math.ceil total,\n  total_log10 = (math.log10 total | math.round 3),\n  total_log2 = (math.log 2 total | math.round 3),\n  total_sqrt = (math.sqrt total | math.round 3),\n  total_ln = (\n    math.ln total\n    math.exp\n    math.round 2\n  ),\n  total_cos = (\n    math.cos total\n    math.acos\n    math.round 2\n  ),\n  total_sin = (\n    math.sin total\n    math.asin\n    math.round 2\n  ),\n  total_tan = (\n    math.tan total\n    math.atan\n    math.round 2\n  ),\n  total_deg = (\n    total\n    math.degrees\n    math.radians\n    math.round 2\n  ),\n  total_square = (\n    total\n    math.pow 2\n    math.round 2\n  ),\n  total_square_op = (total ** 2 | math.round 2),\n}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__pipelines.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip (Only works on Sqlite implementations which have the extension\\n# installed\\n# https://stackoverflow.com/questions/24037982/how-to-use-regexp-in-sqlite)\\n\\nfrom tracks\\n\\nfilter (name ~= \\\"Love\\\")\\nfilter ((milliseconds / 1000 / 60) | in 3..4)\\nsort track_id\\ntake 1..15\\nselect {name, composer}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/pipelines.prql\n---\nfrom tracks\nfilter name ~= \"Love\"\nfilter (milliseconds / 1000 / 60 | in 3..4)\nsort track_id\ntake 1..15\nselect {name, composer}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__read_csv.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip\\n# postgres:skip\\n# mysql:skip\\nfrom (read_csv \\\"data_file_root/media_types.csv\\\")\\nappend (read_json \\\"data_file_root/media_types.json\\\")\\nsort media_type_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/read_csv.prql\n---\nfrom (read_csv \"data_file_root/media_types.csv\")\nappend (\n  read_json \"data_file_root/media_types.json\"\n)\nsort media_type_id\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__set_ops_remove.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nlet distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\\n\\nfrom_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2], [2], [3]] }'\\ndistinct\\nremove (from_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2]] }')\\nsort a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/set_ops_remove.prql\n---\nlet distinct = func rel -> (\n  from t = _param.rel\n  group {t.`*`} (take 1)\n)\n\nfrom_text format:json '{ \"columns\": [\"a\"], \"data\": [[1], [2], [2], [3]] }'\ndistinct\nremove (from_text format:json '{ \"columns\": [\"a\"], \"data\": [[1], [2]] }')\nsort a\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom e=employees\\nfilter first_name != \\\"Mitchell\\\"\\nsort {first_name, last_name}\\n\\n# joining may use HashMerge, which can undo ORDER BY\\njoin manager=employees side:left (e.reports_to == manager.employee_id)\\n\\nselect {e.first_name, e.last_name, manager.first_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort.prql\n---\nfrom e = employees\nfilter first_name != \"Mitchell\"\nsort {first_name, last_name}\njoin side:left manager = employees e.reports_to == manager.employee_id\nselect {e.first_name, e.last_name, manager.first_name}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__sort_2.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from albums\\nselect { AA=album_id, artist_id }\\nsort AA\\nfilter AA >= 25\\njoin artists (==artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_2.prql\n---\nfrom albums\nselect {AA = album_id, artist_id}\nsort AA\nfilter AA >= 25\njoin artists (==artist_id)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__sort_3.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from [{track_id=0, album_id=1, genre_id=2}]\\nselect { AA=track_id, album_id, genre_id }\\nsort AA\\njoin side:left [{album_id=1, album_title=\\\"Songs\\\"}] (==album_id)\\nselect { AA, AT = album_title ?? \\\"unknown\\\", genre_id }\\nfilter AA < 25\\njoin side:left [{genre_id=1, genre_title=\\\"Rock\\\"}] (==genre_id)\\nselect { AA, AT, GT = genre_title ?? \\\"unknown\\\" }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_3.prql\n---\nfrom [{track_id = 0, album_id = 1, genre_id = 2}]\nselect {AA = track_id, album_id, genre_id}\nsort AA\njoin side:left [\n  {album_id = 1, album_title = \"Songs\"},\n] (==album_id)\nselect {\n  AA,\n  AT = album_title ?? \"unknown\",\n  genre_id,\n}\nfilter AA < 25\njoin side:left [\n  {genre_id = 1, genre_title = \"Rock\"},\n] (==genre_id)\nselect {AA, AT, GT = genre_title ?? \"unknown\"}\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__switch.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# glaredb:skip (May be a bag of String type conversion for Postgres Client)\\n# mssql:test\\nfrom tracks\\nsort milliseconds\\nselect display = case [\\n    composer != null => composer,\\n    genre_id < 17 => 'no composer',\\n    true => f'unknown composer'\\n]\\ntake 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/switch.prql\n---\nfrom tracks\nsort milliseconds\nselect display = case [\n  composer != null => composer,\n  genre_id < 17 => \"no composer\",\n  true => f\"unknown composer\",\n]\ntake 10\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {+track_id}\\ntake 3..5\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/take.prql\n---\nfrom tracks\nsort {+track_id}\ntake 3..5\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__text_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# glaredb:skip — TODO: started raising an error on 2024-05-20; see `window.prql`\\n# for more details\\nfrom albums\\nselect {\\n    title,\\n    title_and_spaces = f\\\"  {title}  \\\",\\n    low = (title | text.lower),\\n    up = (title | text.upper),\\n    ltrimmed = (title | text.ltrim),\\n    rtrimmed = (title | text.rtrim),\\n    trimmed = (title | text.trim),\\n    len = (title | text.length),\\n    subs = (title | text.extract 2 5),\\n    replace = (title | text.replace \\\"al\\\" \\\"PIKA\\\"),\\n}\\nsort {title}\\nfilter (title | text.starts_with \\\"Black\\\") || (title | text.contains \\\"Sabbath\\\") || (title | text.ends_with \\\"os\\\")\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/text_module.prql\n---\nfrom albums\nselect {\n  title,\n  title_and_spaces = f\"  {title}  \",\n  low = (title | text.lower),\n  up = (title | text.upper),\n  ltrimmed = (title | text.ltrim),\n  rtrimmed = (title | text.rtrim),\n  trimmed = (title | text.trim),\n  len = (title | text.length),\n  subs = (title | text.extract 2 5),\n  replace = (title | text.replace \"al\" \"PIKA\"),\n}\nsort {title}\nfilter (title | text.starts_with \"Black\") || (\n  title\n  text.contains \"Sabbath\"\n) || (title | text.ends_with \"os\")\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__window.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip problems with DISTINCT ON\\n# glaredb:skip — TODO: started raising an error on 2024-05-20, from https://github.com/PRQL/prql/actions/runs/9154902656/job/25198160283:\\n    # ERROR: This feature is not implemented: Unsupported ast node in sqltorel:\\n    # Substring { expr: Identifier(Ident { value: \\\"title\\\", quote_style: None }),\\n    # substring_from: Some(Value(Number(\\\"2\\\", false))), substring_for:\\n    # Some(Value(Number(\\\"5\\\", false))), special: true }\\nfrom tracks\\ngroup genre_id (\\n  sort milliseconds\\n  derive {\\n    num = row_number this,\\n    total = count this,\\n    last_val = last track_id,\\n  }\\n  take 10\\n)\\nsort {genre_id, milliseconds}\\nselect {track_id, genre_id, num, total, last_val}\\nfilter genre_id >= 22\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/window.prql\n---\nfrom tracks\ngroup genre_id (\n  sort milliseconds\n  derive {\n    num = row_number this,\n    total = count this,\n    last_val = last track_id,\n  }\n  take 10\n)\nsort {genre_id, milliseconds}\nselect {track_id, genre_id, num, total, last_val}\nfilter genre_id >= 22\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__aggregation.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/aggregation.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mysql:skip\"),\n        12..13: NewLine,\n        13..30: Comment(\" clickhouse:skip\"),\n        30..31: NewLine,\n        31..88: Comment(\" glaredb:skip (the string_agg function is not supported)\"),\n        88..89: NewLine,\n        89..93: Ident(\"from\"),\n        94..100: Ident(\"tracks\"),\n        100..101: NewLine,\n        101..107: Ident(\"filter\"),\n        108..116: Ident(\"genre_id\"),\n        117..119: Eq,\n        120..123: Literal(Integer(100)),\n        123..124: NewLine,\n        124..130: Ident(\"derive\"),\n        131..141: Ident(\"empty_name\"),\n        142..143: Control('='),\n        144..148: Ident(\"name\"),\n        149..151: Eq,\n        152..154: Literal(String(\"\")),\n        154..155: NewLine,\n        155..164: Ident(\"aggregate\"),\n        165..166: Control('{'),\n        166..169: Ident(\"sum\"),\n        170..178: Ident(\"track_id\"),\n        178..179: Control(','),\n        180..192: Ident(\"concat_array\"),\n        193..197: Ident(\"name\"),\n        197..198: Control(','),\n        199..202: Ident(\"all\"),\n        203..213: Ident(\"empty_name\"),\n        213..214: Control(','),\n        215..218: Ident(\"any\"),\n        219..229: Ident(\"empty_name\"),\n        229..230: Control('}'),\n        230..231: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__append_select.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/append_select.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..4: Ident(\"from\"),\n        5..13: Ident(\"invoices\"),\n        13..14: NewLine,\n        14..20: Ident(\"select\"),\n        21..22: Control('{'),\n        23..34: Ident(\"customer_id\"),\n        34..35: Control(','),\n        36..46: Ident(\"invoice_id\"),\n        46..47: Control(','),\n        48..63: Ident(\"billing_country\"),\n        64..65: Control('}'),\n        65..66: NewLine,\n        66..70: Ident(\"take\"),\n        71..73: Literal(Integer(10)),\n        73..75: Range { bind_left: true, bind_right: true },\n        75..77: Literal(Integer(15)),\n        77..78: NewLine,\n        78..84: Ident(\"append\"),\n        85..86: Control('('),\n        86..87: NewLine,\n        89..93: Ident(\"from\"),\n        94..102: Ident(\"invoices\"),\n        102..103: NewLine,\n        105..111: Ident(\"select\"),\n        112..113: Control('{'),\n        114..125: Ident(\"customer_id\"),\n        125..126: Control(','),\n        127..137: Ident(\"invoice_id\"),\n        137..138: Control(','),\n        139..154: Ident(\"billing_country\"),\n        155..156: Control('}'),\n        156..157: NewLine,\n        159..163: Ident(\"take\"),\n        164..166: Literal(Integer(40)),\n        166..168: Range { bind_left: true, bind_right: true },\n        168..170: Literal(Integer(45)),\n        170..171: NewLine,\n        171..172: Control(')'),\n        172..173: NewLine,\n        173..179: Ident(\"select\"),\n        180..181: Control('{'),\n        182..197: Ident(\"billing_country\"),\n        197..198: Control(','),\n        199..209: Ident(\"invoice_id\"),\n        210..211: Control('}'),\n        211..212: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__append_select_compute.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_compute.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..4: Ident(\"from\"),\n        5..13: Ident(\"invoices\"),\n        13..14: NewLine,\n        14..20: Ident(\"derive\"),\n        21..26: Ident(\"total\"),\n        27..28: Control('='),\n        29..33: Keyword(\"case\"),\n        34..35: Control('['),\n        35..40: Ident(\"total\"),\n        41..42: Control('<'),\n        43..45: Literal(Integer(10)),\n        46..48: ArrowFat,\n        49..54: Ident(\"total\"),\n        55..56: Control('*'),\n        57..58: Literal(Integer(2)),\n        58..59: Control(','),\n        60..64: Literal(Boolean(true)),\n        65..67: ArrowFat,\n        68..73: Ident(\"total\"),\n        73..74: Control(']'),\n        74..75: NewLine,\n        75..81: Ident(\"select\"),\n        82..83: Control('{'),\n        84..95: Ident(\"customer_id\"),\n        95..96: Control(','),\n        97..107: Ident(\"invoice_id\"),\n        107..108: Control(','),\n        109..114: Ident(\"total\"),\n        115..116: Control('}'),\n        116..117: NewLine,\n        117..121: Ident(\"take\"),\n        122..123: Literal(Integer(5)),\n        123..124: NewLine,\n        124..130: Ident(\"append\"),\n        131..132: Control('('),\n        132..133: NewLine,\n        135..139: Ident(\"from\"),\n        140..153: Ident(\"invoice_items\"),\n        153..154: NewLine,\n        156..162: Ident(\"derive\"),\n        163..173: Ident(\"unit_price\"),\n        174..175: Control('='),\n        176..180: Keyword(\"case\"),\n        181..182: Control('['),\n        182..192: Ident(\"unit_price\"),\n        193..194: Control('<'),\n        195..196: Literal(Integer(1)),\n        197..199: ArrowFat,\n        200..210: Ident(\"unit_price\"),\n        211..212: Control('*'),\n        213..214: Literal(Integer(2)),\n        214..215: Control(','),\n        216..220: Literal(Boolean(true)),\n        221..223: ArrowFat,\n        224..234: Ident(\"unit_price\"),\n        234..235: Control(']'),\n        235..236: NewLine,\n        238..244: Ident(\"select\"),\n        245..246: Control('{'),\n        247..262: Ident(\"invoice_line_id\"),\n        262..263: Control(','),\n        264..274: Ident(\"invoice_id\"),\n        274..275: Control(','),\n        276..286: Ident(\"unit_price\"),\n        287..288: Control('}'),\n        288..289: NewLine,\n        291..295: Ident(\"take\"),\n        296..297: Literal(Integer(5)),\n        297..298: NewLine,\n        298..299: Control(')'),\n        299..300: NewLine,\n        300..306: Ident(\"select\"),\n        307..308: Control('{'),\n        309..310: Ident(\"a\"),\n        311..312: Control('='),\n        313..324: Ident(\"customer_id\"),\n        325..326: Control('*'),\n        327..328: Literal(Integer(2)),\n        328..329: Control(','),\n        330..331: Ident(\"b\"),\n        332..333: Control('='),\n        334..338: Ident(\"math\"),\n        338..339: Control('.'),\n        339..344: Ident(\"round\"),\n        345..346: Literal(Integer(1)),\n        347..348: Control('('),\n        348..358: Ident(\"invoice_id\"),\n        359..360: Control('*'),\n        361..366: Ident(\"total\"),\n        366..367: Control(')'),\n        368..369: Control('}'),\n        369..370: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__append_select_multiple_with_null.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..4: Ident(\"from\"),\n        5..13: Ident(\"invoices\"),\n        13..14: NewLine,\n        14..20: Ident(\"select\"),\n        21..22: Control('{'),\n        23..34: Ident(\"customer_id\"),\n        34..35: Control(','),\n        36..46: Ident(\"invoice_id\"),\n        46..47: Control(','),\n        48..63: Ident(\"billing_country\"),\n        64..65: Control('}'),\n        65..66: NewLine,\n        66..70: Ident(\"take\"),\n        71..72: Literal(Integer(5)),\n        72..73: NewLine,\n        73..79: Ident(\"append\"),\n        80..81: Control('('),\n        81..82: NewLine,\n        84..88: Ident(\"from\"),\n        89..98: Ident(\"employees\"),\n        98..99: NewLine,\n        101..107: Ident(\"select\"),\n        108..109: Control('{'),\n        110..121: Ident(\"employee_id\"),\n        121..122: Control(','),\n        123..134: Ident(\"employee_id\"),\n        134..135: Control(','),\n        136..143: Ident(\"country\"),\n        144..145: Control('}'),\n        145..146: NewLine,\n        148..152: Ident(\"take\"),\n        153..154: Literal(Integer(5)),\n        154..155: NewLine,\n        155..156: Control(')'),\n        156..157: NewLine,\n        157..163: Ident(\"append\"),\n        164..165: Control('('),\n        165..166: NewLine,\n        168..172: Ident(\"from\"),\n        173..186: Ident(\"invoice_items\"),\n        186..187: NewLine,\n        189..195: Ident(\"select\"),\n        196..197: Control('{'),\n        198..213: Ident(\"invoice_line_id\"),\n        213..214: Control(','),\n        215..225: Ident(\"invoice_id\"),\n        225..226: Control(','),\n        227..231: Literal(Null),\n        232..233: Control('}'),\n        233..234: NewLine,\n        236..240: Ident(\"take\"),\n        241..242: Literal(Integer(5)),\n        242..243: NewLine,\n        243..244: Control(')'),\n        244..245: NewLine,\n        245..251: Ident(\"select\"),\n        252..253: Control('{'),\n        254..269: Ident(\"billing_country\"),\n        269..270: Control(','),\n        271..281: Ident(\"invoice_id\"),\n        282..283: Control('}'),\n        283..284: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__append_select_nulls.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_nulls.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..4: Ident(\"from\"),\n        5..13: Ident(\"invoices\"),\n        13..14: NewLine,\n        14..20: Ident(\"select\"),\n        21..22: Control('{'),\n        22..27: Ident(\"an_id\"),\n        28..29: Control('='),\n        30..40: Ident(\"invoice_id\"),\n        40..41: Control(','),\n        42..46: Ident(\"name\"),\n        47..48: Control('='),\n        49..53: Literal(Null),\n        53..54: Control('}'),\n        54..55: NewLine,\n        55..59: Ident(\"take\"),\n        60..61: Literal(Integer(2)),\n        61..62: NewLine,\n        62..68: Ident(\"append\"),\n        69..70: Control('('),\n        70..71: NewLine,\n        73..77: Ident(\"from\"),\n        78..87: Ident(\"employees\"),\n        87..88: NewLine,\n        90..96: Ident(\"select\"),\n        97..98: Control('{'),\n        98..103: Ident(\"an_id\"),\n        104..105: Control('='),\n        106..110: Literal(Null),\n        110..111: Control(','),\n        112..116: Ident(\"name\"),\n        117..118: Control('='),\n        119..129: Ident(\"first_name\"),\n        129..130: Control('}'),\n        130..131: NewLine,\n        133..137: Ident(\"take\"),\n        138..139: Literal(Integer(2)),\n        139..140: NewLine,\n        140..141: Control(')'),\n        141..142: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__append_select_simple.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_simple.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..4: Ident(\"from\"),\n        5..13: Ident(\"invoices\"),\n        13..14: NewLine,\n        14..20: Ident(\"select\"),\n        21..22: Control('{'),\n        23..33: Ident(\"invoice_id\"),\n        33..34: Control(','),\n        35..50: Ident(\"billing_country\"),\n        51..52: Control('}'),\n        52..53: NewLine,\n        53..59: Ident(\"append\"),\n        60..61: Control('('),\n        61..62: NewLine,\n        64..68: Ident(\"from\"),\n        69..77: Ident(\"invoices\"),\n        77..78: NewLine,\n        80..86: Ident(\"select\"),\n        87..88: Control('{'),\n        89..99: Ident(\"invoice_id\"),\n        100..101: Control('='),\n        102..114: Ident(\"invoice_id\"),\n        115..116: Control('+'),\n        117..120: Literal(Integer(100)),\n        120..121: Control(','),\n        122..137: Ident(\"billing_country\"),\n        138..139: Control('}'),\n        139..140: NewLine,\n        140..141: Control(')'),\n        141..142: NewLine,\n        142..148: Ident(\"filter\"),\n        149..150: Control('('),\n        150..165: Ident(\"billing_country\"),\n        166..167: Control('|'),\n        168..172: Ident(\"text\"),\n        172..173: Control('.'),\n        173..184: Ident(\"starts_with\"),\n        184..185: Control('('),\n        185..188: Literal(String(\"I\")),\n        188..189: Control(')'),\n        189..190: Control(')'),\n        190..191: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__arithmetic.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/arithmetic.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..17: Ident(\"from\"),\n        18..19: Control('['),\n        19..20: NewLine,\n        24..25: Control('{'),\n        26..28: Ident(\"id\"),\n        29..30: Control('='),\n        31..32: Literal(Integer(1)),\n        32..33: Control(','),\n        34..39: Ident(\"x_int\"),\n        40..41: Control('='),\n        43..45: Literal(Integer(13)),\n        45..46: Control(','),\n        47..54: Ident(\"x_float\"),\n        55..56: Control('='),\n        58..62: Literal(Float(13.0)),\n        62..63: Control(','),\n        64..69: Ident(\"k_int\"),\n        70..71: Control('='),\n        73..74: Literal(Integer(5)),\n        74..75: Control(','),\n        76..83: Ident(\"k_float\"),\n        84..85: Control('='),\n        87..90: Literal(Float(5.0)),\n        91..92: Control('}'),\n        92..93: Control(','),\n        93..94: NewLine,\n        98..99: Control('{'),\n        100..102: Ident(\"id\"),\n        103..104: Control('='),\n        105..106: Literal(Integer(2)),\n        106..107: Control(','),\n        108..113: Ident(\"x_int\"),\n        114..115: Control('='),\n        116..117: Control('-'),\n        117..119: Literal(Integer(13)),\n        119..120: Control(','),\n        121..128: Ident(\"x_float\"),\n        129..130: Control('='),\n        131..132: Control('-'),\n        132..136: Literal(Float(13.0)),\n        136..137: Control(','),\n        138..143: Ident(\"k_int\"),\n        144..145: Control('='),\n        147..148: Literal(Integer(5)),\n        148..149: Control(','),\n        150..157: Ident(\"k_float\"),\n        158..159: Control('='),\n        161..164: Literal(Float(5.0)),\n        165..166: Control('}'),\n        166..167: Control(','),\n        167..168: NewLine,\n        172..173: Control('{'),\n        174..176: Ident(\"id\"),\n        177..178: Control('='),\n        179..180: Literal(Integer(3)),\n        180..181: Control(','),\n        182..187: Ident(\"x_int\"),\n        188..189: Control('='),\n        191..193: Literal(Integer(13)),\n        193..194: Control(','),\n        195..202: Ident(\"x_float\"),\n        203..204: Control('='),\n        206..210: Literal(Float(13.0)),\n        210..211: Control(','),\n        212..217: Ident(\"k_int\"),\n        218..219: Control('='),\n        220..221: Control('-'),\n        221..222: Literal(Integer(5)),\n        222..223: Control(','),\n        224..231: Ident(\"k_float\"),\n        232..233: Control('='),\n        234..235: Control('-'),\n        235..238: Literal(Float(5.0)),\n        239..240: Control('}'),\n        240..241: Control(','),\n        241..242: NewLine,\n        246..247: Control('{'),\n        248..250: Ident(\"id\"),\n        251..252: Control('='),\n        253..254: Literal(Integer(4)),\n        254..255: Control(','),\n        256..261: Ident(\"x_int\"),\n        262..263: Control('='),\n        264..265: Control('-'),\n        265..267: Literal(Integer(13)),\n        267..268: Control(','),\n        269..276: Ident(\"x_float\"),\n        277..278: Control('='),\n        279..280: Control('-'),\n        280..284: Literal(Float(13.0)),\n        284..285: Control(','),\n        286..291: Ident(\"k_int\"),\n        292..293: Control('='),\n        294..295: Control('-'),\n        295..296: Literal(Integer(5)),\n        296..297: Control(','),\n        298..305: Ident(\"k_float\"),\n        306..307: Control('='),\n        308..309: Control('-'),\n        309..312: Literal(Float(5.0)),\n        313..314: Control('}'),\n        314..315: Control(','),\n        315..316: NewLine,\n        316..317: Control(']'),\n        317..318: NewLine,\n        318..324: Ident(\"select\"),\n        325..326: Control('{'),\n        326..327: NewLine,\n        331..333: Ident(\"id\"),\n        333..334: Control(','),\n        334..335: NewLine,\n        335..336: NewLine,\n        340..345: Ident(\"x_int\"),\n        346..347: Control('/'),\n        348..353: Ident(\"k_int\"),\n        353..354: Control(','),\n        354..355: NewLine,\n        359..364: Ident(\"x_int\"),\n        365..366: Control('/'),\n        367..374: Ident(\"k_float\"),\n        374..375: Control(','),\n        375..376: NewLine,\n        380..387: Ident(\"x_float\"),\n        388..389: Control('/'),\n        390..395: Ident(\"k_int\"),\n        395..396: Control(','),\n        396..397: NewLine,\n        401..408: Ident(\"x_float\"),\n        409..410: Control('/'),\n        411..418: Ident(\"k_float\"),\n        418..419: Control(','),\n        419..420: NewLine,\n        420..421: NewLine,\n        425..429: Ident(\"q_ii\"),\n        430..431: Control('='),\n        432..437: Ident(\"x_int\"),\n        438..440: DivInt,\n        441..446: Ident(\"k_int\"),\n        446..447: Control(','),\n        447..448: NewLine,\n        452..456: Ident(\"q_if\"),\n        457..458: Control('='),\n        459..464: Ident(\"x_int\"),\n        465..467: DivInt,\n        468..475: Ident(\"k_float\"),\n        475..476: Control(','),\n        476..477: NewLine,\n        481..485: Ident(\"q_fi\"),\n        486..487: Control('='),\n        488..495: Ident(\"x_float\"),\n        496..498: DivInt,\n        499..504: Ident(\"k_int\"),\n        504..505: Control(','),\n        505..506: NewLine,\n        510..514: Ident(\"q_ff\"),\n        515..516: Control('='),\n        517..524: Ident(\"x_float\"),\n        525..527: DivInt,\n        528..535: Ident(\"k_float\"),\n        535..536: Control(','),\n        536..537: NewLine,\n        537..538: NewLine,\n        542..546: Ident(\"r_ii\"),\n        547..548: Control('='),\n        549..554: Ident(\"x_int\"),\n        555..556: Control('%'),\n        557..562: Ident(\"k_int\"),\n        562..563: Control(','),\n        563..564: NewLine,\n        568..572: Ident(\"r_if\"),\n        573..574: Control('='),\n        575..580: Ident(\"x_int\"),\n        581..582: Control('%'),\n        583..590: Ident(\"k_float\"),\n        590..591: Control(','),\n        591..592: NewLine,\n        596..600: Ident(\"r_fi\"),\n        601..602: Control('='),\n        603..610: Ident(\"x_float\"),\n        611..612: Control('%'),\n        613..618: Ident(\"k_int\"),\n        618..619: Control(','),\n        619..620: NewLine,\n        624..628: Ident(\"r_ff\"),\n        629..630: Control('='),\n        631..638: Ident(\"x_float\"),\n        639..640: Control('%'),\n        641..648: Ident(\"k_float\"),\n        648..649: Control(','),\n        649..650: NewLine,\n        650..651: NewLine,\n        655..656: Control('('),\n        656..660: Ident(\"q_ii\"),\n        661..662: Control('*'),\n        663..668: Ident(\"k_int\"),\n        669..670: Control('+'),\n        671..675: Ident(\"r_ii\"),\n        676..677: Control('|'),\n        678..682: Ident(\"math\"),\n        682..683: Control('.'),\n        683..688: Ident(\"round\"),\n        689..690: Literal(Integer(0)),\n        690..691: Control(')'),\n        691..692: Control(','),\n        692..693: NewLine,\n        697..698: Control('('),\n        698..702: Ident(\"q_if\"),\n        703..704: Control('*'),\n        705..712: Ident(\"k_float\"),\n        713..714: Control('+'),\n        715..719: Ident(\"r_if\"),\n        720..721: Control('|'),\n        722..726: Ident(\"math\"),\n        726..727: Control('.'),\n        727..732: Ident(\"round\"),\n        733..734: Literal(Integer(0)),\n        734..735: Control(')'),\n        735..736: Control(','),\n        736..737: NewLine,\n        741..742: Control('('),\n        742..746: Ident(\"q_fi\"),\n        747..748: Control('*'),\n        749..754: Ident(\"k_int\"),\n        755..756: Control('+'),\n        757..761: Ident(\"r_fi\"),\n        762..763: Control('|'),\n        764..768: Ident(\"math\"),\n        768..769: Control('.'),\n        769..774: Ident(\"round\"),\n        775..776: Literal(Integer(0)),\n        776..777: Control(')'),\n        777..778: Control(','),\n        778..779: NewLine,\n        783..784: Control('('),\n        784..788: Ident(\"q_ff\"),\n        789..790: Control('*'),\n        791..798: Ident(\"k_float\"),\n        799..800: Control('+'),\n        801..805: Ident(\"r_ff\"),\n        806..807: Control('|'),\n        808..812: Ident(\"math\"),\n        812..813: Control('.'),\n        813..818: Ident(\"round\"),\n        819..820: Literal(Integer(0)),\n        820..821: Control(')'),\n        821..822: Control(','),\n        822..823: NewLine,\n        823..824: Control('}'),\n        824..825: NewLine,\n        825..829: Ident(\"sort\"),\n        830..832: Ident(\"id\"),\n        832..833: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__cast.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/cast.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..17: Ident(\"from\"),\n        18..24: Ident(\"tracks\"),\n        24..25: NewLine,\n        25..29: Ident(\"sort\"),\n        30..31: Control('{'),\n        31..32: Control('-'),\n        32..37: Ident(\"bytes\"),\n        37..38: Control('}'),\n        38..39: NewLine,\n        39..45: Ident(\"select\"),\n        46..47: Control('{'),\n        47..48: NewLine,\n        52..56: Ident(\"name\"),\n        56..57: Control(','),\n        57..58: NewLine,\n        62..65: Ident(\"bin\"),\n        66..67: Control('='),\n        68..69: Control('('),\n        69..70: Control('('),\n        70..78: Ident(\"album_id\"),\n        79..80: Control('|'),\n        81..83: Ident(\"as\"),\n        84..88: Ident(\"REAL\"),\n        88..89: Control(')'),\n        90..91: Control('*'),\n        92..94: Literal(Integer(99)),\n        94..95: Control(')'),\n        95..96: NewLine,\n        96..97: Control('}'),\n        97..98: NewLine,\n        98..102: Ident(\"take\"),\n        103..105: Literal(Integer(20)),\n        105..106: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__constants_only.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/constants_only.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..4: Ident(\"from\"),\n        5..11: Ident(\"genres\"),\n        11..12: NewLine,\n        12..16: Ident(\"take\"),\n        17..19: Literal(Integer(10)),\n        19..20: NewLine,\n        20..26: Ident(\"filter\"),\n        27..31: Literal(Boolean(true)),\n        31..32: NewLine,\n        32..36: Ident(\"take\"),\n        37..39: Literal(Integer(20)),\n        39..40: NewLine,\n        40..46: Ident(\"filter\"),\n        47..51: Literal(Boolean(true)),\n        51..52: NewLine,\n        52..58: Ident(\"select\"),\n        59..60: Ident(\"d\"),\n        61..62: Control('='),\n        63..65: Literal(Integer(10)),\n        65..66: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__date_to_text.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/date_to_text.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..14: Comment(\" generic:skip\"),\n        14..15: NewLine,\n        15..29: Comment(\" glaredb:skip\"),\n        29..30: NewLine,\n        30..43: Comment(\" sqlite:skip\"),\n        43..44: NewLine,\n        44..56: Comment(\" mssql:test\"),\n        56..57: NewLine,\n        57..61: Ident(\"from\"),\n        62..70: Ident(\"invoices\"),\n        70..71: NewLine,\n        71..75: Ident(\"take\"),\n        76..78: Literal(Integer(20)),\n        78..79: NewLine,\n        79..85: Ident(\"select\"),\n        86..87: Control('{'),\n        87..88: NewLine,\n        92..94: Ident(\"d1\"),\n        95..96: Control('='),\n        97..98: Control('('),\n        98..110: Ident(\"invoice_date\"),\n        111..112: Control('|'),\n        113..117: Ident(\"date\"),\n        117..118: Control('.'),\n        118..125: Ident(\"to_text\"),\n        126..136: Literal(String(\"%Y/%m/%d\")),\n        136..137: Control(')'),\n        137..138: Control(','),\n        138..139: NewLine,\n        143..145: Ident(\"d2\"),\n        146..147: Control('='),\n        148..149: Control('('),\n        149..161: Ident(\"invoice_date\"),\n        162..163: Control('|'),\n        164..168: Ident(\"date\"),\n        168..169: Control('.'),\n        169..176: Ident(\"to_text\"),\n        177..181: Literal(String(\"%F\")),\n        181..182: Control(')'),\n        182..183: Control(','),\n        183..184: NewLine,\n        188..190: Ident(\"d3\"),\n        191..192: Control('='),\n        193..194: Control('('),\n        194..206: Ident(\"invoice_date\"),\n        207..208: Control('|'),\n        209..213: Ident(\"date\"),\n        213..214: Control('.'),\n        214..221: Ident(\"to_text\"),\n        222..226: Literal(String(\"%D\")),\n        226..227: Control(')'),\n        227..228: Control(','),\n        228..229: NewLine,\n        233..235: Ident(\"d4\"),\n        236..237: Control('='),\n        238..239: Control('('),\n        239..251: Ident(\"invoice_date\"),\n        252..253: Control('|'),\n        254..258: Ident(\"date\"),\n        258..259: Control('.'),\n        259..266: Ident(\"to_text\"),\n        267..280: Literal(String(\"%H:%M:%S.%f\")),\n        280..281: Control(')'),\n        281..282: Control(','),\n        282..283: NewLine,\n        287..289: Ident(\"d5\"),\n        290..291: Control('='),\n        292..293: Control('('),\n        293..305: Ident(\"invoice_date\"),\n        306..307: Control('|'),\n        308..312: Ident(\"date\"),\n        312..313: Control('.'),\n        313..320: Ident(\"to_text\"),\n        321..325: Literal(String(\"%r\")),\n        325..326: Control(')'),\n        326..327: Control(','),\n        327..328: NewLine,\n        332..334: Ident(\"d6\"),\n        335..336: Control('='),\n        337..338: Control('('),\n        338..350: Ident(\"invoice_date\"),\n        351..352: Control('|'),\n        353..357: Ident(\"date\"),\n        357..358: Control('.'),\n        358..365: Ident(\"to_text\"),\n        366..380: Literal(String(\"%A %B %-d %Y\")),\n        380..381: Control(')'),\n        381..382: Control(','),\n        382..383: NewLine,\n        387..389: Ident(\"d7\"),\n        390..391: Control('='),\n        392..393: Control('('),\n        393..405: Ident(\"invoice_date\"),\n        406..407: Control('|'),\n        408..412: Ident(\"date\"),\n        412..413: Control('.'),\n        413..420: Ident(\"to_text\"),\n        421..451: Literal(String(\"%a, %-d %b %Y at %I:%M:%S %p\")),\n        451..452: Control(')'),\n        452..453: Control(','),\n        453..454: NewLine,\n        458..460: Ident(\"d8\"),\n        461..462: Control('='),\n        463..464: Control('('),\n        464..476: Ident(\"invoice_date\"),\n        477..478: Control('|'),\n        479..483: Ident(\"date\"),\n        483..484: Control('.'),\n        484..491: Ident(\"to_text\"),\n        492..496: Literal(String(\"%+\")),\n        496..497: Control(')'),\n        497..498: Control(','),\n        498..499: NewLine,\n        503..505: Ident(\"d9\"),\n        506..507: Control('='),\n        508..509: Control('('),\n        509..521: Ident(\"invoice_date\"),\n        522..523: Control('|'),\n        524..528: Ident(\"date\"),\n        528..529: Control('.'),\n        529..536: Ident(\"to_text\"),\n        537..549: Literal(String(\"%-d/%-m/%y\")),\n        549..550: Control(')'),\n        550..551: Control(','),\n        551..552: NewLine,\n        556..559: Ident(\"d10\"),\n        560..561: Control('='),\n        562..563: Control('('),\n        563..575: Ident(\"invoice_date\"),\n        576..577: Control('|'),\n        578..582: Ident(\"date\"),\n        582..583: Control('.'),\n        583..590: Ident(\"to_text\"),\n        591..603: Literal(String(\"%-Hh %Mmin\")),\n        603..604: Control(')'),\n        604..605: Control(','),\n        605..606: NewLine,\n        610..613: Ident(\"d11\"),\n        614..615: Control('='),\n        616..617: Control('('),\n        617..629: Ident(\"invoice_date\"),\n        630..631: Control('|'),\n        632..636: Ident(\"date\"),\n        636..637: Control('.'),\n        637..644: Ident(\"to_text\"),\n        645..654: Literal(String(\"%M'%S\\\"\")),\n        654..655: Control(')'),\n        655..656: Control(','),\n        656..657: NewLine,\n        661..664: Ident(\"d12\"),\n        665..666: Control('='),\n        667..668: Control('('),\n        668..680: Ident(\"invoice_date\"),\n        681..682: Control('|'),\n        683..687: Ident(\"date\"),\n        687..688: Control('.'),\n        688..695: Ident(\"to_text\"),\n        696..714: Literal(String(\"100%% in %d days\")),\n        714..715: Control(')'),\n        715..716: Control(','),\n        716..717: NewLine,\n        717..718: Control('}'),\n        718..719: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__distinct.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/distinct.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..17: Ident(\"from\"),\n        18..24: Ident(\"tracks\"),\n        24..25: NewLine,\n        25..31: Ident(\"select\"),\n        32..33: Control('{'),\n        33..41: Ident(\"album_id\"),\n        41..42: Control(','),\n        43..51: Ident(\"genre_id\"),\n        51..52: Control('}'),\n        52..53: NewLine,\n        53..58: Ident(\"group\"),\n        59..65: Ident(\"tracks\"),\n        65..66: Control('.'),\n        66..67: Control('*'),\n        68..69: Control('('),\n        69..73: Ident(\"take\"),\n        74..75: Literal(Integer(1)),\n        75..76: Control(')'),\n        76..77: NewLine,\n        77..81: Ident(\"sort\"),\n        82..88: Ident(\"tracks\"),\n        88..89: Control('.'),\n        89..90: Control('*'),\n        90..91: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__distinct_on.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/distinct_on.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..17: Ident(\"from\"),\n        18..24: Ident(\"tracks\"),\n        24..25: NewLine,\n        25..31: Ident(\"select\"),\n        32..33: Control('{'),\n        33..41: Ident(\"genre_id\"),\n        41..42: Control(','),\n        43..56: Ident(\"media_type_id\"),\n        56..57: Control(','),\n        58..66: Ident(\"album_id\"),\n        66..67: Control('}'),\n        67..68: NewLine,\n        68..73: Ident(\"group\"),\n        74..75: Control('{'),\n        75..83: Ident(\"genre_id\"),\n        83..84: Control(','),\n        85..98: Ident(\"media_type_id\"),\n        98..99: Control('}'),\n        100..101: Control('('),\n        101..105: Ident(\"sort\"),\n        106..107: Control('{'),\n        107..108: Control('-'),\n        108..116: Ident(\"album_id\"),\n        116..117: Control('}'),\n        118..119: Control('|'),\n        120..124: Ident(\"take\"),\n        125..126: Literal(Integer(1)),\n        126..127: Control(')'),\n        127..128: NewLine,\n        128..132: Ident(\"sort\"),\n        133..134: Control('{'),\n        134..135: Control('-'),\n        135..143: Ident(\"genre_id\"),\n        143..144: Control(','),\n        145..158: Ident(\"media_type_id\"),\n        158..159: Control('}'),\n        159..160: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__genre_counts.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/genre_counts.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..103: Comment(\" clickhouse:skip (ClickHouse prefers aliases to column names https://github.com/PRQL/prql/issues/2827)\"),\n        103..104: NewLine,\n        104..116: Comment(\" mssql:test\"),\n        116..117: NewLine,\n        117..120: Keyword(\"let\"),\n        121..132: Ident(\"genre_count\"),\n        133..134: Control('='),\n        135..136: Control('('),\n        136..137: NewLine,\n        141..145: Ident(\"from\"),\n        146..152: Ident(\"genres\"),\n        152..153: NewLine,\n        157..166: Ident(\"aggregate\"),\n        167..168: Control('{'),\n        168..169: Ident(\"a\"),\n        170..171: Control('='),\n        172..177: Ident(\"count\"),\n        178..182: Ident(\"name\"),\n        182..183: Control('}'),\n        183..184: NewLine,\n        184..185: Control(')'),\n        185..186: NewLine,\n        186..187: NewLine,\n        187..191: Ident(\"from\"),\n        192..203: Ident(\"genre_count\"),\n        203..204: NewLine,\n        204..210: Ident(\"filter\"),\n        211..212: Ident(\"a\"),\n        213..214: Control('>'),\n        215..216: Literal(Integer(0)),\n        216..217: NewLine,\n        217..223: Ident(\"select\"),\n        224..225: Ident(\"a\"),\n        226..227: Control('='),\n        228..229: Control('-'),\n        229..230: Ident(\"a\"),\n        230..231: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__group_all.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/group_all.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..17: Ident(\"from\"),\n        18..19: Ident(\"a\"),\n        19..20: Control('='),\n        20..26: Ident(\"albums\"),\n        26..27: NewLine,\n        27..31: Ident(\"take\"),\n        32..34: Literal(Integer(10)),\n        34..35: NewLine,\n        35..39: Ident(\"join\"),\n        40..46: Ident(\"tracks\"),\n        47..48: Control('('),\n        48..50: Eq,\n        50..58: Ident(\"album_id\"),\n        58..59: Control(')'),\n        59..60: NewLine,\n        60..65: Ident(\"group\"),\n        66..67: Control('{'),\n        67..68: Ident(\"a\"),\n        68..69: Control('.'),\n        69..77: Ident(\"album_id\"),\n        77..78: Control(','),\n        79..80: Ident(\"a\"),\n        80..81: Control('.'),\n        81..86: Ident(\"title\"),\n        86..87: Control('}'),\n        88..89: Control('('),\n        89..98: Ident(\"aggregate\"),\n        99..104: Ident(\"price\"),\n        105..106: Control('='),\n        107..108: Control('('),\n        108..111: Ident(\"sum\"),\n        112..118: Ident(\"tracks\"),\n        118..119: Control('.'),\n        119..129: Ident(\"unit_price\"),\n        130..131: Control('|'),\n        132..136: Ident(\"math\"),\n        136..137: Control('.'),\n        137..142: Ident(\"round\"),\n        143..144: Literal(Integer(2)),\n        144..145: Control(')'),\n        145..146: Control(')'),\n        146..147: NewLine,\n        147..151: Ident(\"sort\"),\n        152..160: Ident(\"album_id\"),\n        160..161: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__group_sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..17: Ident(\"from\"),\n        18..24: Ident(\"tracks\"),\n        24..25: NewLine,\n        25..31: Ident(\"derive\"),\n        32..33: Ident(\"d\"),\n        34..35: Control('='),\n        36..44: Ident(\"album_id\"),\n        45..46: Control('+'),\n        47..48: Literal(Integer(1)),\n        48..49: NewLine,\n        49..54: Ident(\"group\"),\n        55..56: Ident(\"d\"),\n        57..58: Control('('),\n        58..59: NewLine,\n        63..72: Ident(\"aggregate\"),\n        73..74: Control('{'),\n        74..75: NewLine,\n        83..85: Ident(\"n1\"),\n        86..87: Control('='),\n        88..89: Control('('),\n        89..97: Ident(\"track_id\"),\n        98..99: Control('|'),\n        100..103: Ident(\"sum\"),\n        103..104: Control(')'),\n        104..105: Control(','),\n        105..106: NewLine,\n        110..111: Control('}'),\n        111..112: NewLine,\n        112..113: Control(')'),\n        113..114: NewLine,\n        114..118: Ident(\"sort\"),\n        119..120: Ident(\"d\"),\n        120..121: NewLine,\n        121..125: Ident(\"take\"),\n        126..128: Literal(Integer(10)),\n        128..129: NewLine,\n        129..135: Ident(\"select\"),\n        136..137: Control('{'),\n        138..140: Ident(\"d1\"),\n        141..142: Control('='),\n        143..144: Ident(\"d\"),\n        144..145: Control(','),\n        146..148: Ident(\"n1\"),\n        149..150: Control('}'),\n        150..151: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__group_sort_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_derive_select_join.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..46: Interpolation('s', \"SELECT album_id,title,artist_id FROM albums\"),\n        46..47: NewLine,\n        47..52: Ident(\"group\"),\n        53..54: Control('{'),\n        54..63: Ident(\"artist_id\"),\n        63..64: Control('}'),\n        65..66: Control('('),\n        66..75: Ident(\"aggregate\"),\n        76..77: Control('{'),\n        78..95: Ident(\"album_title_count\"),\n        96..97: Control('='),\n        98..103: Ident(\"count\"),\n        104..108: Ident(\"this\"),\n        108..109: Control('.'),\n        109..116: Ident(\"title\"),\n        116..117: Control('}'),\n        117..118: Control(')'),\n        118..119: NewLine,\n        119..123: Ident(\"sort\"),\n        124..125: Control('{'),\n        125..129: Ident(\"this\"),\n        129..130: Control('.'),\n        130..139: Ident(\"artist_id\"),\n        139..140: Control(','),\n        141..145: Ident(\"this\"),\n        145..146: Control('.'),\n        146..163: Ident(\"album_title_count\"),\n        163..164: Control('}'),\n        164..165: NewLine,\n        165..171: Ident(\"derive\"),\n        172..173: Control('{'),\n        173..188: Ident(\"new_album_count\"),\n        189..190: Control('='),\n        191..195: Ident(\"this\"),\n        195..196: Control('.'),\n        196..213: Ident(\"album_title_count\"),\n        213..214: Control('}'),\n        214..215: NewLine,\n        215..221: Ident(\"select\"),\n        222..223: Control('{'),\n        223..227: Ident(\"this\"),\n        227..228: Control('.'),\n        228..237: Ident(\"artist_id\"),\n        237..238: Control(','),\n        239..243: Ident(\"this\"),\n        243..244: Control('.'),\n        244..259: Ident(\"new_album_count\"),\n        259..260: Control('}'),\n        260..261: NewLine,\n        261..265: Ident(\"join\"),\n        266..270: Ident(\"side\"),\n        270..271: Control(':'),\n        271..275: Ident(\"left\"),\n        276..277: Control('('),\n        278..330: Interpolation('s', \"SELECT artist_id,name as artist_name FROM artists\"),\n        331..332: Control(')'),\n        333..334: Control('('),\n        334..338: Ident(\"this\"),\n        338..339: Control('.'),\n        339..348: Ident(\"artist_id\"),\n        349..351: Eq,\n        352..356: Ident(\"that\"),\n        356..357: Control('.'),\n        357..366: Ident(\"artist_id\"),\n        366..367: Control(')'),\n        367..368: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__group_sort_filter_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_filter_derive_select_join.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..46: Interpolation('s', \"SELECT album_id,title,artist_id FROM albums\"),\n        46..47: NewLine,\n        47..52: Ident(\"group\"),\n        53..54: Control('{'),\n        54..63: Ident(\"artist_id\"),\n        63..64: Control('}'),\n        65..66: Control('('),\n        66..75: Ident(\"aggregate\"),\n        76..77: Control('{'),\n        78..95: Ident(\"album_title_count\"),\n        96..97: Control('='),\n        98..103: Ident(\"count\"),\n        104..108: Ident(\"this\"),\n        108..109: Control('.'),\n        109..116: Ident(\"title\"),\n        116..117: Control('}'),\n        117..118: Control(')'),\n        118..119: NewLine,\n        119..123: Ident(\"sort\"),\n        124..125: Control('{'),\n        125..129: Ident(\"this\"),\n        129..130: Control('.'),\n        130..139: Ident(\"artist_id\"),\n        139..140: Control(','),\n        141..145: Ident(\"this\"),\n        145..146: Control('.'),\n        146..163: Ident(\"album_title_count\"),\n        163..164: Control('}'),\n        164..165: NewLine,\n        165..171: Ident(\"filter\"),\n        172..173: Control('('),\n        173..177: Ident(\"this\"),\n        177..178: Control('.'),\n        178..195: Ident(\"album_title_count\"),\n        195..196: Control(')'),\n        197..198: Control('>'),\n        199..201: Literal(Integer(10)),\n        201..202: NewLine,\n        202..208: Ident(\"derive\"),\n        209..210: Control('{'),\n        210..225: Ident(\"new_album_count\"),\n        226..227: Control('='),\n        228..232: Ident(\"this\"),\n        232..233: Control('.'),\n        233..250: Ident(\"album_title_count\"),\n        250..251: Control('}'),\n        251..252: NewLine,\n        252..258: Ident(\"select\"),\n        259..260: Control('{'),\n        260..264: Ident(\"this\"),\n        264..265: Control('.'),\n        265..274: Ident(\"artist_id\"),\n        274..275: Control(','),\n        276..280: Ident(\"this\"),\n        280..281: Control('.'),\n        281..296: Ident(\"new_album_count\"),\n        296..297: Control('}'),\n        297..298: NewLine,\n        298..302: Ident(\"join\"),\n        303..307: Ident(\"side\"),\n        307..308: Control(':'),\n        308..312: Ident(\"left\"),\n        313..314: Control('('),\n        315..367: Interpolation('s', \"SELECT artist_id,name as artist_name FROM artists\"),\n        368..369: Control(')'),\n        370..371: Control('('),\n        371..375: Ident(\"this\"),\n        375..376: Control('.'),\n        376..385: Ident(\"artist_id\"),\n        386..388: Eq,\n        389..393: Ident(\"that\"),\n        393..394: Control('.'),\n        394..403: Ident(\"artist_id\"),\n        403..404: Control(')'),\n        404..405: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__group_sort_limit_take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_limit_take.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..62: Comment(\" Compute the 3 longest songs for each genre and sort by genre\"),\n        62..63: NewLine,\n        63..75: Comment(\" mssql:test\"),\n        75..76: NewLine,\n        76..80: Ident(\"from\"),\n        81..87: Ident(\"tracks\"),\n        87..88: NewLine,\n        88..94: Ident(\"select\"),\n        95..96: Control('{'),\n        96..104: Ident(\"genre_id\"),\n        104..105: Control(','),\n        105..117: Ident(\"milliseconds\"),\n        117..118: Control('}'),\n        118..119: NewLine,\n        119..124: Ident(\"group\"),\n        125..126: Control('{'),\n        126..134: Ident(\"genre_id\"),\n        134..135: Control('}'),\n        136..137: Control('('),\n        137..138: NewLine,\n        140..144: Ident(\"sort\"),\n        145..146: Control('{'),\n        146..147: Control('-'),\n        147..159: Ident(\"milliseconds\"),\n        159..160: Control('}'),\n        160..161: NewLine,\n        163..167: Ident(\"take\"),\n        168..169: Literal(Integer(3)),\n        169..170: NewLine,\n        170..171: Control(')'),\n        171..172: NewLine,\n        172..176: Ident(\"join\"),\n        177..183: Ident(\"genres\"),\n        184..185: Control('('),\n        185..187: Eq,\n        187..195: Ident(\"genre_id\"),\n        195..196: Control(')'),\n        196..197: NewLine,\n        197..203: Ident(\"select\"),\n        204..205: Control('{'),\n        205..209: Ident(\"name\"),\n        209..210: Control(','),\n        211..223: Ident(\"milliseconds\"),\n        223..224: Control('}'),\n        224..225: NewLine,\n        225..229: Ident(\"sort\"),\n        230..231: Control('{'),\n        231..232: Control('+'),\n        232..236: Ident(\"name\"),\n        236..237: Control(','),\n        237..238: Control('-'),\n        238..250: Ident(\"milliseconds\"),\n        250..251: Control('}'),\n        251..252: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__invoice_totals.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/invoice_totals.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..56: Comment(\" clickhouse:skip (clickhouse doesn't have lag function)\"),\n        56..57: NewLine,\n        57..58: NewLine,\n        58..130: DocComment(\" Calculate a number of metrics about the sales of tracks in each city.\"),\n        130..131: NewLine,\n        131..135: Ident(\"from\"),\n        136..137: Ident(\"i\"),\n        137..138: Control('='),\n        138..146: Ident(\"invoices\"),\n        146..147: NewLine,\n        147..151: Ident(\"join\"),\n        152..154: Ident(\"ii\"),\n        154..155: Control('='),\n        155..168: Ident(\"invoice_items\"),\n        169..170: Control('('),\n        170..172: Eq,\n        172..182: Ident(\"invoice_id\"),\n        182..183: Control(')'),\n        183..184: NewLine,\n        184..190: Ident(\"derive\"),\n        191..192: Control('{'),\n        192..193: NewLine,\n        197..201: Ident(\"city\"),\n        202..203: Control('='),\n        204..205: Ident(\"i\"),\n        205..206: Control('.'),\n        206..218: Ident(\"billing_city\"),\n        218..219: Control(','),\n        219..220: NewLine,\n        224..230: Ident(\"street\"),\n        231..232: Control('='),\n        233..234: Ident(\"i\"),\n        234..235: Control('.'),\n        235..250: Ident(\"billing_address\"),\n        250..251: Control(','),\n        251..252: NewLine,\n        252..253: Control('}'),\n        253..254: NewLine,\n        254..259: Ident(\"group\"),\n        260..261: Control('{'),\n        261..265: Ident(\"city\"),\n        265..266: Control(','),\n        267..273: Ident(\"street\"),\n        273..274: Control('}'),\n        275..276: Control('('),\n        276..277: NewLine,\n        281..287: Ident(\"derive\"),\n        288..293: Ident(\"total\"),\n        294..295: Control('='),\n        296..298: Ident(\"ii\"),\n        298..299: Control('.'),\n        299..309: Ident(\"unit_price\"),\n        310..311: Control('*'),\n        312..314: Ident(\"ii\"),\n        314..315: Control('.'),\n        315..323: Ident(\"quantity\"),\n        323..324: NewLine,\n        328..337: Ident(\"aggregate\"),\n        338..339: Control('{'),\n        339..340: NewLine,\n        348..358: Ident(\"num_orders\"),\n        359..360: Control('='),\n        361..375: Ident(\"count_distinct\"),\n        376..377: Ident(\"i\"),\n        377..378: Control('.'),\n        378..388: Ident(\"invoice_id\"),\n        388..389: Control(','),\n        389..390: NewLine,\n        398..408: Ident(\"num_tracks\"),\n        409..410: Control('='),\n        411..414: Ident(\"sum\"),\n        415..417: Ident(\"ii\"),\n        417..418: Control('.'),\n        418..426: Ident(\"quantity\"),\n        426..427: Control(','),\n        427..428: NewLine,\n        436..447: Ident(\"total_price\"),\n        448..449: Control('='),\n        450..453: Ident(\"sum\"),\n        454..459: Ident(\"total\"),\n        459..460: Control(','),\n        460..461: NewLine,\n        465..466: Control('}'),\n        466..467: NewLine,\n        467..468: Control(')'),\n        468..469: NewLine,\n        469..474: Ident(\"group\"),\n        475..476: Control('{'),\n        476..480: Ident(\"city\"),\n        480..481: Control('}'),\n        482..483: Control('('),\n        483..484: NewLine,\n        488..492: Ident(\"sort\"),\n        493..499: Ident(\"street\"),\n        499..500: NewLine,\n        504..510: Ident(\"window\"),\n        511..520: Ident(\"expanding\"),\n        520..521: Control(':'),\n        521..525: Literal(Boolean(true)),\n        526..527: Control('('),\n        527..528: NewLine,\n        536..542: Ident(\"derive\"),\n        543..544: Control('{'),\n        544..568: Ident(\"running_total_num_tracks\"),\n        569..570: Control('='),\n        571..574: Ident(\"sum\"),\n        575..585: Ident(\"num_tracks\"),\n        585..586: Control('}'),\n        586..587: NewLine,\n        591..592: Control(')'),\n        592..593: NewLine,\n        593..594: Control(')'),\n        594..595: NewLine,\n        595..599: Ident(\"sort\"),\n        600..601: Control('{'),\n        601..605: Ident(\"city\"),\n        605..606: Control(','),\n        607..613: Ident(\"street\"),\n        613..614: Control('}'),\n        614..615: NewLine,\n        615..621: Ident(\"derive\"),\n        622..623: Control('{'),\n        623..643: Ident(\"num_tracks_last_week\"),\n        644..645: Control('='),\n        646..649: Ident(\"lag\"),\n        650..651: Literal(Integer(7)),\n        652..662: Ident(\"num_tracks\"),\n        662..663: Control('}'),\n        663..664: NewLine,\n        664..670: Ident(\"select\"),\n        671..672: Control('{'),\n        672..673: NewLine,\n        677..681: Ident(\"city\"),\n        681..682: Control(','),\n        682..683: NewLine,\n        687..693: Ident(\"street\"),\n        693..694: Control(','),\n        694..695: NewLine,\n        699..709: Ident(\"num_orders\"),\n        709..710: Control(','),\n        710..711: NewLine,\n        715..725: Ident(\"num_tracks\"),\n        725..726: Control(','),\n        726..727: NewLine,\n        731..755: Ident(\"running_total_num_tracks\"),\n        755..756: Control(','),\n        756..757: NewLine,\n        761..781: Ident(\"num_tracks_last_week\"),\n        781..782: NewLine,\n        782..783: Control('}'),\n        783..784: NewLine,\n        784..788: Ident(\"take\"),\n        789..791: Literal(Integer(20)),\n        791..792: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__loop_01.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/loop_01.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..47: Comment(\" clickhouse:skip (DB::Exception: Syntax error)\"),\n        47..48: NewLine,\n        48..161: Comment(\" glaredb:skip (DataFusion does not support recursive CTEs https://github.com/apache/arrow-datafusion/issues/462)\"),\n        161..162: NewLine,\n        162..166: Ident(\"from\"),\n        167..168: Control('['),\n        168..169: Control('{'),\n        169..170: Ident(\"n\"),\n        171..172: Control('='),\n        173..174: Literal(Integer(1)),\n        174..175: Control('}'),\n        175..176: Control(']'),\n        176..177: NewLine,\n        177..183: Ident(\"select\"),\n        184..185: Ident(\"n\"),\n        186..187: Control('='),\n        188..189: Ident(\"n\"),\n        190..191: Control('-'),\n        192..193: Literal(Integer(2)),\n        193..194: NewLine,\n        194..198: Ident(\"loop\"),\n        199..200: Control('('),\n        200..206: Ident(\"filter\"),\n        207..208: Ident(\"n\"),\n        209..210: Control('<'),\n        211..212: Literal(Integer(4)),\n        213..214: Control('|'),\n        215..221: Ident(\"select\"),\n        222..223: Ident(\"n\"),\n        224..225: Control('='),\n        226..227: Ident(\"n\"),\n        228..229: Control('+'),\n        230..231: Literal(Integer(1)),\n        231..232: Control(')'),\n        232..233: NewLine,\n        233..239: Ident(\"select\"),\n        240..241: Ident(\"n\"),\n        242..243: Control('='),\n        244..245: Ident(\"n\"),\n        246..247: Control('*'),\n        248..249: Literal(Integer(2)),\n        249..250: NewLine,\n        250..254: Ident(\"sort\"),\n        255..256: Ident(\"n\"),\n        256..257: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__math_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/math_module.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..81: Comment(\" sqlite:skip (see https://github.com/rusqlite/rusqlite/issues/1211)\"),\n        81..82: NewLine,\n        82..86: Ident(\"from\"),\n        87..95: Ident(\"invoices\"),\n        95..96: NewLine,\n        96..100: Ident(\"take\"),\n        101..102: Literal(Integer(5)),\n        102..103: NewLine,\n        103..109: Ident(\"select\"),\n        110..111: Control('{'),\n        111..112: NewLine,\n        116..130: Ident(\"total_original\"),\n        131..132: Control('='),\n        133..134: Control('('),\n        134..139: Ident(\"total\"),\n        140..141: Control('|'),\n        142..146: Ident(\"math\"),\n        146..147: Control('.'),\n        147..152: Ident(\"round\"),\n        153..154: Literal(Integer(2)),\n        154..155: Control(')'),\n        155..156: Control(','),\n        156..157: NewLine,\n        161..168: Ident(\"total_x\"),\n        169..170: Control('='),\n        171..172: Control('('),\n        172..176: Ident(\"math\"),\n        176..177: Control('.'),\n        177..179: Ident(\"pi\"),\n        180..181: Control('-'),\n        182..187: Ident(\"total\"),\n        188..189: Control('|'),\n        190..194: Ident(\"math\"),\n        194..195: Control('.'),\n        195..200: Ident(\"round\"),\n        201..202: Literal(Integer(2)),\n        203..204: Control('|'),\n        205..209: Ident(\"math\"),\n        209..210: Control('.'),\n        210..213: Ident(\"abs\"),\n        213..214: Control(')'),\n        214..215: Control(','),\n        215..216: NewLine,\n        220..231: Ident(\"total_floor\"),\n        232..233: Control('='),\n        234..235: Control('('),\n        235..239: Ident(\"math\"),\n        239..240: Control('.'),\n        240..245: Ident(\"floor\"),\n        246..251: Ident(\"total\"),\n        251..252: Control(')'),\n        252..253: Control(','),\n        253..254: NewLine,\n        258..268: Ident(\"total_ceil\"),\n        269..270: Control('='),\n        271..272: Control('('),\n        272..276: Ident(\"math\"),\n        276..277: Control('.'),\n        277..281: Ident(\"ceil\"),\n        282..287: Ident(\"total\"),\n        287..288: Control(')'),\n        288..289: Control(','),\n        289..290: NewLine,\n        294..305: Ident(\"total_log10\"),\n        306..307: Control('='),\n        308..309: Control('('),\n        309..313: Ident(\"math\"),\n        313..314: Control('.'),\n        314..319: Ident(\"log10\"),\n        320..325: Ident(\"total\"),\n        326..327: Control('|'),\n        328..332: Ident(\"math\"),\n        332..333: Control('.'),\n        333..338: Ident(\"round\"),\n        339..340: Literal(Integer(3)),\n        340..341: Control(')'),\n        341..342: Control(','),\n        342..343: NewLine,\n        347..357: Ident(\"total_log2\"),\n        358..359: Control('='),\n        360..361: Control('('),\n        361..365: Ident(\"math\"),\n        365..366: Control('.'),\n        366..369: Ident(\"log\"),\n        370..371: Literal(Integer(2)),\n        372..377: Ident(\"total\"),\n        378..379: Control('|'),\n        380..384: Ident(\"math\"),\n        384..385: Control('.'),\n        385..390: Ident(\"round\"),\n        391..392: Literal(Integer(3)),\n        392..393: Control(')'),\n        393..394: Control(','),\n        394..395: NewLine,\n        399..409: Ident(\"total_sqrt\"),\n        410..411: Control('='),\n        412..413: Control('('),\n        413..417: Ident(\"math\"),\n        417..418: Control('.'),\n        418..422: Ident(\"sqrt\"),\n        423..428: Ident(\"total\"),\n        429..430: Control('|'),\n        431..435: Ident(\"math\"),\n        435..436: Control('.'),\n        436..441: Ident(\"round\"),\n        442..443: Literal(Integer(3)),\n        443..444: Control(')'),\n        444..445: Control(','),\n        445..446: NewLine,\n        450..458: Ident(\"total_ln\"),\n        459..460: Control('='),\n        461..462: Control('('),\n        462..466: Ident(\"math\"),\n        466..467: Control('.'),\n        467..469: Ident(\"ln\"),\n        470..475: Ident(\"total\"),\n        476..477: Control('|'),\n        478..482: Ident(\"math\"),\n        482..483: Control('.'),\n        483..486: Ident(\"exp\"),\n        487..488: Control('|'),\n        489..493: Ident(\"math\"),\n        493..494: Control('.'),\n        494..499: Ident(\"round\"),\n        500..501: Literal(Integer(2)),\n        501..502: Control(')'),\n        502..503: Control(','),\n        503..504: NewLine,\n        508..517: Ident(\"total_cos\"),\n        518..519: Control('='),\n        520..521: Control('('),\n        521..525: Ident(\"math\"),\n        525..526: Control('.'),\n        526..529: Ident(\"cos\"),\n        530..535: Ident(\"total\"),\n        536..537: Control('|'),\n        538..542: Ident(\"math\"),\n        542..543: Control('.'),\n        543..547: Ident(\"acos\"),\n        548..549: Control('|'),\n        550..554: Ident(\"math\"),\n        554..555: Control('.'),\n        555..560: Ident(\"round\"),\n        561..562: Literal(Integer(2)),\n        562..563: Control(')'),\n        563..564: Control(','),\n        564..565: NewLine,\n        569..578: Ident(\"total_sin\"),\n        579..580: Control('='),\n        581..582: Control('('),\n        582..586: Ident(\"math\"),\n        586..587: Control('.'),\n        587..590: Ident(\"sin\"),\n        591..596: Ident(\"total\"),\n        597..598: Control('|'),\n        599..603: Ident(\"math\"),\n        603..604: Control('.'),\n        604..608: Ident(\"asin\"),\n        609..610: Control('|'),\n        611..615: Ident(\"math\"),\n        615..616: Control('.'),\n        616..621: Ident(\"round\"),\n        622..623: Literal(Integer(2)),\n        623..624: Control(')'),\n        624..625: Control(','),\n        625..626: NewLine,\n        630..639: Ident(\"total_tan\"),\n        640..641: Control('='),\n        642..643: Control('('),\n        643..647: Ident(\"math\"),\n        647..648: Control('.'),\n        648..651: Ident(\"tan\"),\n        652..657: Ident(\"total\"),\n        658..659: Control('|'),\n        660..664: Ident(\"math\"),\n        664..665: Control('.'),\n        665..669: Ident(\"atan\"),\n        670..671: Control('|'),\n        672..676: Ident(\"math\"),\n        676..677: Control('.'),\n        677..682: Ident(\"round\"),\n        683..684: Literal(Integer(2)),\n        684..685: Control(')'),\n        685..686: Control(','),\n        686..687: NewLine,\n        691..700: Ident(\"total_deg\"),\n        701..702: Control('='),\n        703..704: Control('('),\n        704..709: Ident(\"total\"),\n        710..711: Control('|'),\n        712..716: Ident(\"math\"),\n        716..717: Control('.'),\n        717..724: Ident(\"degrees\"),\n        725..726: Control('|'),\n        727..731: Ident(\"math\"),\n        731..732: Control('.'),\n        732..739: Ident(\"radians\"),\n        740..741: Control('|'),\n        742..746: Ident(\"math\"),\n        746..747: Control('.'),\n        747..752: Ident(\"round\"),\n        753..754: Literal(Integer(2)),\n        754..755: Control(')'),\n        755..756: Control(','),\n        756..757: NewLine,\n        761..773: Ident(\"total_square\"),\n        774..775: Control('='),\n        776..777: Control('('),\n        777..782: Ident(\"total\"),\n        783..784: Control('|'),\n        785..789: Ident(\"math\"),\n        789..790: Control('.'),\n        790..793: Ident(\"pow\"),\n        794..795: Literal(Integer(2)),\n        796..797: Control('|'),\n        798..802: Ident(\"math\"),\n        802..803: Control('.'),\n        803..808: Ident(\"round\"),\n        809..810: Literal(Integer(2)),\n        810..811: Control(')'),\n        811..812: Control(','),\n        812..813: NewLine,\n        817..832: Ident(\"total_square_op\"),\n        833..834: Control('='),\n        835..836: Control('('),\n        836..837: Control('('),\n        837..842: Ident(\"total\"),\n        843..845: Pow,\n        846..847: Literal(Integer(2)),\n        847..848: Control(')'),\n        849..850: Control('|'),\n        851..855: Ident(\"math\"),\n        855..856: Control('.'),\n        856..861: Ident(\"round\"),\n        862..863: Literal(Integer(2)),\n        863..864: Control(')'),\n        864..865: Control(','),\n        865..866: NewLine,\n        866..867: Control('}'),\n        867..868: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__pipelines.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/pipelines.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..76: Comment(\" sqlite:skip (Only works on Sqlite implementations which have the extension\"),\n        76..77: NewLine,\n        77..88: Comment(\" installed\"),\n        88..89: NewLine,\n        89..164: Comment(\" https://stackoverflow.com/questions/24037982/how-to-use-regexp-in-sqlite)\"),\n        164..165: NewLine,\n        165..166: NewLine,\n        166..170: Ident(\"from\"),\n        171..177: Ident(\"tracks\"),\n        177..178: NewLine,\n        178..179: NewLine,\n        179..185: Ident(\"filter\"),\n        186..187: Control('('),\n        187..191: Ident(\"name\"),\n        192..194: RegexSearch,\n        195..201: Literal(String(\"Love\")),\n        201..202: Control(')'),\n        202..203: NewLine,\n        203..209: Ident(\"filter\"),\n        210..211: Control('('),\n        211..212: Control('('),\n        212..224: Ident(\"milliseconds\"),\n        225..226: Control('/'),\n        227..231: Literal(Integer(1000)),\n        232..233: Control('/'),\n        234..236: Literal(Integer(60)),\n        236..237: Control(')'),\n        238..239: Control('|'),\n        240..242: Ident(\"in\"),\n        243..244: Literal(Integer(3)),\n        244..246: Range { bind_left: true, bind_right: true },\n        246..247: Literal(Integer(4)),\n        247..248: Control(')'),\n        248..249: NewLine,\n        249..253: Ident(\"sort\"),\n        254..262: Ident(\"track_id\"),\n        262..263: NewLine,\n        263..267: Ident(\"take\"),\n        268..269: Literal(Integer(1)),\n        269..271: Range { bind_left: true, bind_right: true },\n        271..273: Literal(Integer(15)),\n        273..274: NewLine,\n        274..280: Ident(\"select\"),\n        281..282: Control('{'),\n        282..286: Ident(\"name\"),\n        286..287: Control(','),\n        288..296: Ident(\"composer\"),\n        296..297: Control('}'),\n        297..298: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__read_csv.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/read_csv.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..13: Comment(\" sqlite:skip\"),\n        13..14: NewLine,\n        14..29: Comment(\" postgres:skip\"),\n        29..30: NewLine,\n        30..42: Comment(\" mysql:skip\"),\n        42..43: NewLine,\n        43..47: Ident(\"from\"),\n        48..49: Control('('),\n        49..57: Ident(\"read_csv\"),\n        58..90: Literal(String(\"data_file_root/media_types.csv\")),\n        90..91: Control(')'),\n        91..92: NewLine,\n        92..98: Ident(\"append\"),\n        99..100: Control('('),\n        100..109: Ident(\"read_json\"),\n        110..143: Literal(String(\"data_file_root/media_types.json\")),\n        143..144: Control(')'),\n        144..145: NewLine,\n        145..149: Ident(\"sort\"),\n        150..163: Ident(\"media_type_id\"),\n        163..164: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__set_ops_remove.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/set_ops_remove.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..16: Keyword(\"let\"),\n        17..25: Ident(\"distinct\"),\n        26..27: Control('='),\n        28..31: Ident(\"rel\"),\n        32..34: ArrowThin,\n        35..36: Control('('),\n        36..40: Ident(\"from\"),\n        41..42: Ident(\"t\"),\n        43..44: Control('='),\n        45..51: Ident(\"_param\"),\n        51..52: Control('.'),\n        52..55: Ident(\"rel\"),\n        56..57: Control('|'),\n        58..63: Ident(\"group\"),\n        64..65: Control('{'),\n        65..66: Ident(\"t\"),\n        66..67: Control('.'),\n        67..68: Control('*'),\n        68..69: Control('}'),\n        70..71: Control('('),\n        71..75: Ident(\"take\"),\n        76..77: Literal(Integer(1)),\n        77..78: Control(')'),\n        78..79: Control(')'),\n        79..80: NewLine,\n        80..81: NewLine,\n        81..90: Ident(\"from_text\"),\n        91..97: Ident(\"format\"),\n        97..98: Control(':'),\n        98..102: Ident(\"json\"),\n        103..155: Literal(String(\"{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2], [2], [3]] }\")),\n        155..156: NewLine,\n        156..164: Ident(\"distinct\"),\n        164..165: NewLine,\n        165..171: Ident(\"remove\"),\n        172..173: Control('('),\n        173..182: Ident(\"from_text\"),\n        183..189: Ident(\"format\"),\n        189..190: Control(':'),\n        190..194: Ident(\"json\"),\n        195..237: Literal(String(\"{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2]] }\")),\n        237..238: Control(')'),\n        238..239: NewLine,\n        239..243: Ident(\"sort\"),\n        244..245: Ident(\"a\"),\n        245..246: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/sort.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..17: Ident(\"from\"),\n        18..19: Ident(\"e\"),\n        19..20: Control('='),\n        20..29: Ident(\"employees\"),\n        29..30: NewLine,\n        30..36: Ident(\"filter\"),\n        37..47: Ident(\"first_name\"),\n        48..50: Ne,\n        51..61: Literal(String(\"Mitchell\")),\n        61..62: NewLine,\n        62..66: Ident(\"sort\"),\n        67..68: Control('{'),\n        68..78: Ident(\"first_name\"),\n        78..79: Control(','),\n        80..89: Ident(\"last_name\"),\n        89..90: Control('}'),\n        90..91: NewLine,\n        91..92: NewLine,\n        92..144: Comment(\" joining may use HashMerge, which can undo ORDER BY\"),\n        144..145: NewLine,\n        145..149: Ident(\"join\"),\n        150..157: Ident(\"manager\"),\n        157..158: Control('='),\n        158..167: Ident(\"employees\"),\n        168..172: Ident(\"side\"),\n        172..173: Control(':'),\n        173..177: Ident(\"left\"),\n        178..179: Control('('),\n        179..180: Ident(\"e\"),\n        180..181: Control('.'),\n        181..191: Ident(\"reports_to\"),\n        192..194: Eq,\n        195..202: Ident(\"manager\"),\n        202..203: Control('.'),\n        203..214: Ident(\"employee_id\"),\n        214..215: Control(')'),\n        215..216: NewLine,\n        216..217: NewLine,\n        217..223: Ident(\"select\"),\n        224..225: Control('{'),\n        225..226: Ident(\"e\"),\n        226..227: Control('.'),\n        227..237: Ident(\"first_name\"),\n        237..238: Control(','),\n        239..240: Ident(\"e\"),\n        240..241: Control('.'),\n        241..250: Ident(\"last_name\"),\n        250..251: Control(','),\n        252..259: Ident(\"manager\"),\n        259..260: Control('.'),\n        260..270: Ident(\"first_name\"),\n        270..271: Control('}'),\n        271..272: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__sort_2.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/sort_2.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..4: Ident(\"from\"),\n        5..11: Ident(\"albums\"),\n        11..12: NewLine,\n        12..18: Ident(\"select\"),\n        19..20: Control('{'),\n        21..23: Ident(\"AA\"),\n        23..24: Control('='),\n        24..32: Ident(\"album_id\"),\n        32..33: Control(','),\n        34..43: Ident(\"artist_id\"),\n        44..45: Control('}'),\n        45..46: NewLine,\n        46..50: Ident(\"sort\"),\n        51..53: Ident(\"AA\"),\n        53..54: NewLine,\n        54..60: Ident(\"filter\"),\n        61..63: Ident(\"AA\"),\n        64..66: Gte,\n        67..69: Literal(Integer(25)),\n        69..70: NewLine,\n        70..74: Ident(\"join\"),\n        75..82: Ident(\"artists\"),\n        83..84: Control('('),\n        84..86: Eq,\n        86..95: Ident(\"artist_id\"),\n        95..96: Control(')'),\n        96..97: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__sort_3.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/sort_3.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..4: Ident(\"from\"),\n        5..6: Control('['),\n        6..7: Control('{'),\n        7..15: Ident(\"track_id\"),\n        15..16: Control('='),\n        16..17: Literal(Integer(0)),\n        17..18: Control(','),\n        19..27: Ident(\"album_id\"),\n        27..28: Control('='),\n        28..29: Literal(Integer(1)),\n        29..30: Control(','),\n        31..39: Ident(\"genre_id\"),\n        39..40: Control('='),\n        40..41: Literal(Integer(2)),\n        41..42: Control('}'),\n        42..43: Control(']'),\n        43..44: NewLine,\n        44..50: Ident(\"select\"),\n        51..52: Control('{'),\n        53..55: Ident(\"AA\"),\n        55..56: Control('='),\n        56..64: Ident(\"track_id\"),\n        64..65: Control(','),\n        66..74: Ident(\"album_id\"),\n        74..75: Control(','),\n        76..84: Ident(\"genre_id\"),\n        85..86: Control('}'),\n        86..87: NewLine,\n        87..91: Ident(\"sort\"),\n        92..94: Ident(\"AA\"),\n        94..95: NewLine,\n        95..99: Ident(\"join\"),\n        100..104: Ident(\"side\"),\n        104..105: Control(':'),\n        105..109: Ident(\"left\"),\n        110..111: Control('['),\n        111..112: Control('{'),\n        112..120: Ident(\"album_id\"),\n        120..121: Control('='),\n        121..122: Literal(Integer(1)),\n        122..123: Control(','),\n        124..135: Ident(\"album_title\"),\n        135..136: Control('='),\n        136..143: Literal(String(\"Songs\")),\n        143..144: Control('}'),\n        144..145: Control(']'),\n        146..147: Control('('),\n        147..149: Eq,\n        149..157: Ident(\"album_id\"),\n        157..158: Control(')'),\n        158..159: NewLine,\n        159..165: Ident(\"select\"),\n        166..167: Control('{'),\n        168..170: Ident(\"AA\"),\n        170..171: Control(','),\n        172..174: Ident(\"AT\"),\n        175..176: Control('='),\n        177..188: Ident(\"album_title\"),\n        189..191: Coalesce,\n        192..201: Literal(String(\"unknown\")),\n        201..202: Control(','),\n        203..211: Ident(\"genre_id\"),\n        212..213: Control('}'),\n        213..214: NewLine,\n        214..220: Ident(\"filter\"),\n        221..223: Ident(\"AA\"),\n        224..225: Control('<'),\n        226..228: Literal(Integer(25)),\n        228..229: NewLine,\n        229..233: Ident(\"join\"),\n        234..238: Ident(\"side\"),\n        238..239: Control(':'),\n        239..243: Ident(\"left\"),\n        244..245: Control('['),\n        245..246: Control('{'),\n        246..254: Ident(\"genre_id\"),\n        254..255: Control('='),\n        255..256: Literal(Integer(1)),\n        256..257: Control(','),\n        258..269: Ident(\"genre_title\"),\n        269..270: Control('='),\n        270..276: Literal(String(\"Rock\")),\n        276..277: Control('}'),\n        277..278: Control(']'),\n        279..280: Control('('),\n        280..282: Eq,\n        282..290: Ident(\"genre_id\"),\n        290..291: Control(')'),\n        291..292: NewLine,\n        292..298: Ident(\"select\"),\n        299..300: Control('{'),\n        301..303: Ident(\"AA\"),\n        303..304: Control(','),\n        305..307: Ident(\"AT\"),\n        307..308: Control(','),\n        309..311: Ident(\"GT\"),\n        312..313: Control('='),\n        314..325: Ident(\"genre_title\"),\n        326..328: Coalesce,\n        329..338: Literal(String(\"unknown\")),\n        339..340: Control('}'),\n        340..341: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__switch.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/switch.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..75: Comment(\" glaredb:skip (May be a bag of String type conversion for Postgres Client)\"),\n        75..76: NewLine,\n        76..88: Comment(\" mssql:test\"),\n        88..89: NewLine,\n        89..93: Ident(\"from\"),\n        94..100: Ident(\"tracks\"),\n        100..101: NewLine,\n        101..105: Ident(\"sort\"),\n        106..118: Ident(\"milliseconds\"),\n        118..119: NewLine,\n        119..125: Ident(\"select\"),\n        126..133: Ident(\"display\"),\n        134..135: Control('='),\n        136..140: Keyword(\"case\"),\n        141..142: Control('['),\n        142..143: NewLine,\n        147..155: Ident(\"composer\"),\n        156..158: Ne,\n        159..163: Literal(Null),\n        164..166: ArrowFat,\n        167..175: Ident(\"composer\"),\n        175..176: Control(','),\n        176..177: NewLine,\n        181..189: Ident(\"genre_id\"),\n        190..191: Control('<'),\n        192..194: Literal(Integer(17)),\n        195..197: ArrowFat,\n        198..211: Literal(String(\"no composer\")),\n        211..212: Control(','),\n        212..213: NewLine,\n        217..221: Literal(Boolean(true)),\n        222..224: ArrowFat,\n        225..244: Interpolation('f', \"unknown composer\"),\n        244..245: NewLine,\n        245..246: Control(']'),\n        246..247: NewLine,\n        247..251: Ident(\"take\"),\n        252..254: Literal(Integer(10)),\n        254..255: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/take.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..17: Ident(\"from\"),\n        18..24: Ident(\"tracks\"),\n        24..25: NewLine,\n        25..29: Ident(\"sort\"),\n        30..31: Control('{'),\n        31..32: Control('+'),\n        32..40: Ident(\"track_id\"),\n        40..41: Control('}'),\n        41..42: NewLine,\n        42..46: Ident(\"take\"),\n        47..48: Literal(Integer(3)),\n        48..50: Range { bind_left: true, bind_right: true },\n        50..51: Literal(Integer(5)),\n        51..52: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__text_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/text_module.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..12: Comment(\" mssql:test\"),\n        12..13: NewLine,\n        13..95: Comment(\" glaredb:skip — TODO: started raising an error on 2024-05-20; see `window.prql`\"),\n        95..96: NewLine,\n        96..114: Comment(\" for more details\"),\n        114..115: NewLine,\n        115..119: Ident(\"from\"),\n        120..126: Ident(\"albums\"),\n        126..127: NewLine,\n        127..133: Ident(\"select\"),\n        134..135: Control('{'),\n        135..136: NewLine,\n        140..145: Ident(\"title\"),\n        145..146: Control(','),\n        146..147: NewLine,\n        151..167: Ident(\"title_and_spaces\"),\n        168..169: Control('='),\n        170..184: Interpolation('f', \"  {title}  \"),\n        184..185: Control(','),\n        185..186: NewLine,\n        190..193: Ident(\"low\"),\n        194..195: Control('='),\n        196..197: Control('('),\n        197..202: Ident(\"title\"),\n        203..204: Control('|'),\n        205..209: Ident(\"text\"),\n        209..210: Control('.'),\n        210..215: Ident(\"lower\"),\n        215..216: Control(')'),\n        216..217: Control(','),\n        217..218: NewLine,\n        222..224: Ident(\"up\"),\n        225..226: Control('='),\n        227..228: Control('('),\n        228..233: Ident(\"title\"),\n        234..235: Control('|'),\n        236..240: Ident(\"text\"),\n        240..241: Control('.'),\n        241..246: Ident(\"upper\"),\n        246..247: Control(')'),\n        247..248: Control(','),\n        248..249: NewLine,\n        253..261: Ident(\"ltrimmed\"),\n        262..263: Control('='),\n        264..265: Control('('),\n        265..270: Ident(\"title\"),\n        271..272: Control('|'),\n        273..277: Ident(\"text\"),\n        277..278: Control('.'),\n        278..283: Ident(\"ltrim\"),\n        283..284: Control(')'),\n        284..285: Control(','),\n        285..286: NewLine,\n        290..298: Ident(\"rtrimmed\"),\n        299..300: Control('='),\n        301..302: Control('('),\n        302..307: Ident(\"title\"),\n        308..309: Control('|'),\n        310..314: Ident(\"text\"),\n        314..315: Control('.'),\n        315..320: Ident(\"rtrim\"),\n        320..321: Control(')'),\n        321..322: Control(','),\n        322..323: NewLine,\n        327..334: Ident(\"trimmed\"),\n        335..336: Control('='),\n        337..338: Control('('),\n        338..343: Ident(\"title\"),\n        344..345: Control('|'),\n        346..350: Ident(\"text\"),\n        350..351: Control('.'),\n        351..355: Ident(\"trim\"),\n        355..356: Control(')'),\n        356..357: Control(','),\n        357..358: NewLine,\n        362..365: Ident(\"len\"),\n        366..367: Control('='),\n        368..369: Control('('),\n        369..374: Ident(\"title\"),\n        375..376: Control('|'),\n        377..381: Ident(\"text\"),\n        381..382: Control('.'),\n        382..388: Ident(\"length\"),\n        388..389: Control(')'),\n        389..390: Control(','),\n        390..391: NewLine,\n        395..399: Ident(\"subs\"),\n        400..401: Control('='),\n        402..403: Control('('),\n        403..408: Ident(\"title\"),\n        409..410: Control('|'),\n        411..415: Ident(\"text\"),\n        415..416: Control('.'),\n        416..423: Ident(\"extract\"),\n        424..425: Literal(Integer(2)),\n        426..427: Literal(Integer(5)),\n        427..428: Control(')'),\n        428..429: Control(','),\n        429..430: NewLine,\n        434..441: Ident(\"replace\"),\n        442..443: Control('='),\n        444..445: Control('('),\n        445..450: Ident(\"title\"),\n        451..452: Control('|'),\n        453..457: Ident(\"text\"),\n        457..458: Control('.'),\n        458..465: Ident(\"replace\"),\n        466..470: Literal(String(\"al\")),\n        471..477: Literal(String(\"PIKA\")),\n        477..478: Control(')'),\n        478..479: Control(','),\n        479..480: NewLine,\n        480..481: Control('}'),\n        481..482: NewLine,\n        482..486: Ident(\"sort\"),\n        487..488: Control('{'),\n        488..493: Ident(\"title\"),\n        493..494: Control('}'),\n        494..495: NewLine,\n        495..501: Ident(\"filter\"),\n        502..503: Control('('),\n        503..508: Ident(\"title\"),\n        509..510: Control('|'),\n        511..515: Ident(\"text\"),\n        515..516: Control('.'),\n        516..527: Ident(\"starts_with\"),\n        528..535: Literal(String(\"Black\")),\n        535..536: Control(')'),\n        537..539: Or,\n        540..541: Control('('),\n        541..546: Ident(\"title\"),\n        547..548: Control('|'),\n        549..553: Ident(\"text\"),\n        553..554: Control('.'),\n        554..562: Ident(\"contains\"),\n        563..572: Literal(String(\"Sabbath\")),\n        572..573: Control(')'),\n        574..576: Or,\n        577..578: Control('('),\n        578..583: Ident(\"title\"),\n        584..585: Control('|'),\n        586..590: Ident(\"text\"),\n        590..591: Control('.'),\n        591..600: Ident(\"ends_with\"),\n        601..605: Literal(String(\"os\")),\n        605..606: Control(')'),\n        606..607: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__lex__window.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: tokens\ninput_file: prqlc/prqlc/tests/integration/queries/window.prql\n---\nTokens(\n    [\n        0..0: Start,\n        0..43: Comment(\" clickhouse:skip problems with DISTINCT ON\"),\n        43..44: NewLine,\n        44..183: Comment(\" glaredb:skip — TODO: started raising an error on 2024-05-20, from https://github.com/PRQL/prql/actions/runs/9154902656/job/25198160283:\"),\n        183..184: NewLine,\n        188..263: Comment(\" ERROR: This feature is not implemented: Unsupported ast node in sqltorel:\"),\n        263..264: NewLine,\n        268..344: Comment(\" Substring { expr: Identifier(Ident { value: \\\"title\\\", quote_style: None }),\"),\n        344..345: NewLine,\n        349..414: Comment(\" substring_from: Some(Value(Number(\\\"2\\\", false))), substring_for:\"),\n        414..415: NewLine,\n        419..469: Comment(\" Some(Value(Number(\\\"5\\\", false))), special: true }\"),\n        469..470: NewLine,\n        470..474: Ident(\"from\"),\n        475..481: Ident(\"tracks\"),\n        481..482: NewLine,\n        482..487: Ident(\"group\"),\n        488..496: Ident(\"genre_id\"),\n        497..498: Control('('),\n        498..499: NewLine,\n        501..505: Ident(\"sort\"),\n        506..518: Ident(\"milliseconds\"),\n        518..519: NewLine,\n        521..527: Ident(\"derive\"),\n        528..529: Control('{'),\n        529..530: NewLine,\n        534..537: Ident(\"num\"),\n        538..539: Control('='),\n        540..550: Ident(\"row_number\"),\n        551..555: Ident(\"this\"),\n        555..556: Control(','),\n        556..557: NewLine,\n        561..566: Ident(\"total\"),\n        567..568: Control('='),\n        569..574: Ident(\"count\"),\n        575..579: Ident(\"this\"),\n        579..580: Control(','),\n        580..581: NewLine,\n        585..593: Ident(\"last_val\"),\n        594..595: Control('='),\n        596..600: Ident(\"last\"),\n        601..609: Ident(\"track_id\"),\n        609..610: Control(','),\n        610..611: NewLine,\n        613..614: Control('}'),\n        614..615: NewLine,\n        617..621: Ident(\"take\"),\n        622..624: Literal(Integer(10)),\n        624..625: NewLine,\n        625..626: Control(')'),\n        626..627: NewLine,\n        627..631: Ident(\"sort\"),\n        632..633: Control('{'),\n        633..641: Ident(\"genre_id\"),\n        641..642: Control(','),\n        643..655: Ident(\"milliseconds\"),\n        655..656: Control('}'),\n        656..657: NewLine,\n        657..663: Ident(\"select\"),\n        664..665: Control('{'),\n        665..673: Ident(\"track_id\"),\n        673..674: Control(','),\n        675..683: Ident(\"genre_id\"),\n        683..684: Control(','),\n        685..688: Ident(\"num\"),\n        688..689: Control(','),\n        690..695: Ident(\"total\"),\n        695..696: Control(','),\n        697..705: Ident(\"last_val\"),\n        705..706: Control('}'),\n        706..707: NewLine,\n        707..713: Ident(\"filter\"),\n        714..722: Ident(\"genre_id\"),\n        723..725: Gte,\n        726..728: Literal(Integer(22)),\n        728..729: NewLine,\n    ],\n)\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__aggregation.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:skip\\n# mysql:skip\\n# clickhouse:skip\\n# glaredb:skip (the string_agg function is not supported)\\nfrom tracks\\nfilter genre_id == 100\\nderive empty_name = name == ''\\naggregate {sum track_id, concat_array name, all empty_name, any empty_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/aggregation.prql\n---\n0,,1,0\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__append_select.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 10..15\\nappend (\\n  from invoices\\n  select { customer_id, invoice_id, billing_country }\\n  take 40..45\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select.prql\n---\nIreland,10\nUnited Kingdom,11\nGermany,12\nUSA,13\nUSA,14\nUSA,15\nGermany,40\nSpain,41\nSweden,42\nUnited Kingdom,43\nAustralia,44\nIndia,45\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__append_select_compute.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nderive total = case [total < 10 => total * 2, true => total]\\nselect { customer_id, invoice_id, total }\\ntake 5\\nappend (\\n  from invoice_items\\n  derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\\n  select { invoice_line_id, invoice_id, unit_price }\\n  take 5\\n)\\nselect { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_compute.prql\n---\n4,4\n8,15.8\n16,35.6\n28,71.3\n46,69.3\n2,2\n4,2\n6,4\n8,4\n10,4\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__append_select_multiple_with_null.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { customer_id, invoice_id, billing_country }\\ntake 5\\nappend (\\n  from employees\\n  select { employee_id, employee_id, country }\\n  take 5\\n)\\nappend (\\n  from invoice_items\\n  select { invoice_line_id, invoice_id, null }\\n  take 5\\n)\\nselect { billing_country, invoice_id }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql\n---\nGermany,1\nNorway,2\nBelgium,3\nCanada,4\nUSA,5\nCanada,1\nCanada,2\nCanada,3\nCanada,4\nCanada,5\n,1\n,1\n,2\n,2\n,2\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__append_select_nulls.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# duckdb:skip\\n# postgres:skip\\n\\nfrom invoices\\nselect {an_id = invoice_id, name = null}\\ntake 2\\nappend (\\n  from employees\\n  select {an_id = null, name = first_name}\\n  take 2\\n)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_nulls.prql\n---\n1,\n2,\n,Andrew\n,Nancy\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__append_select_simple.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from invoices\\nselect { invoice_id, billing_country }\\nappend (\\n  from invoices\\n  select { invoice_id = `invoice_id` + 100, billing_country }\\n)\\nfilter (billing_country | text.starts_with(\\\"I\\\"))\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/append_select_simple.prql\n---\n10,Ireland\n23,India\n45,India\n62,Ireland\n63,Italy\n86,Italy\n97,India\n108,Italy\n120,India\n131,India\n160,Italy\n183,Ireland\n186,India\n194,Ireland\n218,India\n229,India\n249,Ireland\n281,Italy\n284,India\n292,Italy\n315,India\n338,India\n347,Italy\n360,India\n378,Ireland\n401,Ireland\n412,India\n110,Ireland\n123,India\n145,India\n162,Ireland\n163,Italy\n186,Italy\n197,India\n208,Italy\n220,India\n231,India\n260,Italy\n283,Ireland\n286,India\n294,Ireland\n318,India\n329,India\n349,Ireland\n381,Italy\n384,India\n392,Italy\n415,India\n438,India\n447,Italy\n460,India\n478,Ireland\n501,Ireland\n512,India\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__arithmetic.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom [\\n    { id = 1, x_int =  13, x_float =  13.0, k_int =  5, k_float =  5.0 },\\n    { id = 2, x_int = -13, x_float = -13.0, k_int =  5, k_float =  5.0 },\\n    { id = 3, x_int =  13, x_float =  13.0, k_int = -5, k_float = -5.0 },\\n    { id = 4, x_int = -13, x_float = -13.0, k_int = -5, k_float = -5.0 },\\n]\\nselect {\\n    id,\\n\\n    x_int / k_int,\\n    x_int / k_float,\\n    x_float / k_int,\\n    x_float / k_float,\\n\\n    q_ii = x_int // k_int,\\n    q_if = x_int // k_float,\\n    q_fi = x_float // k_int,\\n    q_ff = x_float // k_float,\\n\\n    r_ii = x_int % k_int,\\n    r_if = x_int % k_float,\\n    r_fi = x_float % k_int,\\n    r_ff = x_float % k_float,\\n\\n    (q_ii * k_int + r_ii | math.round 0),\\n    (q_if * k_float + r_if | math.round 0),\\n    (q_fi * k_int + r_fi | math.round 0),\\n    (q_ff * k_float + r_ff | math.round 0),\\n}\\nsort id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/arithmetic.prql\n---\n1,2.6,2.6,2.6,2.6,2,2,2,2,3,3,3,3,13,13,13,13\n2,-2.6,-2.6,-2.6,-2.6,-2,-2,-2,-2,-3,-3,-3,-3,-13,-13,-13,-13\n3,-2.6,-2.6,-2.6,-2.6,-2,-2,-2,-2,3,3,3,3,13,13,13,13\n4,2.6,2.6,2.6,2.6,2,2,2,2,-3,-3,-3,-3,-13,-13,-13,-13\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__cast.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {-bytes}\\nselect {\\n    name,\\n    bin = ((album_id | as REAL) * 99)\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/cast.prql\n---\nThrough a Looking Glass,22671\nOccupation / Precipice,22473\nThe Young Lords,25047\nThe Man With Nine Lives,25047\nDave,22869\nThe Magnificent Warriors,25047\nThe Lost Warrior,25047\nMaternity Leave,22869\nBattlestar Galactica, Pt. 3,25047\nThe Woman King,22473\nMurder On the Rising Star,25047\nThrough the Looking Glass, Pt. 2,22671\nThe Man from Tallahassee,22671\nBetter Halves,22572\nTricia Tanaka Is Dead,22671\nExperiment In Terra,25047\nThe Gun On Ice Planet Zero, Pt. 2,25047\nLockdown,22869\nMan of Science, Man of Faith (Premiere),22869\nRun!,22572\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__constants_only.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from genres\\ntake 10\\nfilter true\\ntake 20\\nfilter true\\nselect d = 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/constants_only.prql\n---\n10\n10\n10\n10\n10\n10\n10\n10\n10\n10\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__date_to_text.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# generic:skip\\n# glaredb:skip\\n# sqlite:skip\\n# mssql:test\\nfrom invoices\\ntake 20\\nselect {\\n    d1 = (invoice_date | date.to_text \\\"%Y/%m/%d\\\"),\\n    d2 = (invoice_date | date.to_text \\\"%F\\\"),\\n    d3 = (invoice_date | date.to_text \\\"%D\\\"),\\n    d4 = (invoice_date | date.to_text \\\"%H:%M:%S.%f\\\"),\\n    d5 = (invoice_date | date.to_text \\\"%r\\\"),\\n    d6 = (invoice_date | date.to_text \\\"%A %B %-d %Y\\\"),\\n    d7 = (invoice_date | date.to_text \\\"%a, %-d %b %Y at %I:%M:%S %p\\\"),\\n    d8 = (invoice_date | date.to_text \\\"%+\\\"),\\n    d9 = (invoice_date | date.to_text \\\"%-d/%-m/%y\\\"),\\n    d10 = (invoice_date | date.to_text \\\"%-Hh %Mmin\\\"),\\n    d11 = (invoice_date | date.to_text \\\"%M'%S\\\\\\\"\\\"),\\n    d12 = (invoice_date | date.to_text \\\"100%% in %d days\\\"),\\n}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/date_to_text.prql\nsnapshot_kind: text\n---\n2009/01/01,2009-01-01,01/01/09,00:00:00.000000,12:00:00 AM,Thursday January 1 2009,Thu, 1 Jan 2009 at 12:00:00 AM,2009-01-01T00:00:00.000000Z,1/1/09,0h 00min,00'00\",100% in 01 days\n2009/01/02,2009-01-02,01/02/09,00:00:00.000000,12:00:00 AM,Friday January 2 2009,Fri, 2 Jan 2009 at 12:00:00 AM,2009-01-02T00:00:00.000000Z,2/1/09,0h 00min,00'00\",100% in 02 days\n2009/01/03,2009-01-03,01/03/09,00:00:00.000000,12:00:00 AM,Saturday January 3 2009,Sat, 3 Jan 2009 at 12:00:00 AM,2009-01-03T00:00:00.000000Z,3/1/09,0h 00min,00'00\",100% in 03 days\n2009/01/06,2009-01-06,01/06/09,00:00:00.000000,12:00:00 AM,Tuesday January 6 2009,Tue, 6 Jan 2009 at 12:00:00 AM,2009-01-06T00:00:00.000000Z,6/1/09,0h 00min,00'00\",100% in 06 days\n2009/01/11,2009-01-11,01/11/09,00:00:00.000000,12:00:00 AM,Sunday January 11 2009,Sun, 11 Jan 2009 at 12:00:00 AM,2009-01-11T00:00:00.000000Z,11/1/09,0h 00min,00'00\",100% in 11 days\n2009/01/19,2009-01-19,01/19/09,00:00:00.000000,12:00:00 AM,Monday January 19 2009,Mon, 19 Jan 2009 at 12:00:00 AM,2009-01-19T00:00:00.000000Z,19/1/09,0h 00min,00'00\",100% in 19 days\n2009/02/01,2009-02-01,02/01/09,00:00:00.000000,12:00:00 AM,Sunday February 1 2009,Sun, 1 Feb 2009 at 12:00:00 AM,2009-02-01T00:00:00.000000Z,1/2/09,0h 00min,00'00\",100% in 01 days\n2009/02/01,2009-02-01,02/01/09,00:00:00.000000,12:00:00 AM,Sunday February 1 2009,Sun, 1 Feb 2009 at 12:00:00 AM,2009-02-01T00:00:00.000000Z,1/2/09,0h 00min,00'00\",100% in 01 days\n2009/02/02,2009-02-02,02/02/09,00:00:00.000000,12:00:00 AM,Monday February 2 2009,Mon, 2 Feb 2009 at 12:00:00 AM,2009-02-02T00:00:00.000000Z,2/2/09,0h 00min,00'00\",100% in 02 days\n2009/02/03,2009-02-03,02/03/09,00:00:00.000000,12:00:00 AM,Tuesday February 3 2009,Tue, 3 Feb 2009 at 12:00:00 AM,2009-02-03T00:00:00.000000Z,3/2/09,0h 00min,00'00\",100% in 03 days\n2009/02/06,2009-02-06,02/06/09,00:00:00.000000,12:00:00 AM,Friday February 6 2009,Fri, 6 Feb 2009 at 12:00:00 AM,2009-02-06T00:00:00.000000Z,6/2/09,0h 00min,00'00\",100% in 06 days\n2009/02/11,2009-02-11,02/11/09,00:00:00.000000,12:00:00 AM,Wednesday February 11 2009,Wed, 11 Feb 2009 at 12:00:00 AM,2009-02-11T00:00:00.000000Z,11/2/09,0h 00min,00'00\",100% in 11 days\n2009/02/19,2009-02-19,02/19/09,00:00:00.000000,12:00:00 AM,Thursday February 19 2009,Thu, 19 Feb 2009 at 12:00:00 AM,2009-02-19T00:00:00.000000Z,19/2/09,0h 00min,00'00\",100% in 19 days\n2009/03/04,2009-03-04,03/04/09,00:00:00.000000,12:00:00 AM,Wednesday March 4 2009,Wed, 4 Mar 2009 at 12:00:00 AM,2009-03-04T00:00:00.000000Z,4/3/09,0h 00min,00'00\",100% in 04 days\n2009/03/04,2009-03-04,03/04/09,00:00:00.000000,12:00:00 AM,Wednesday March 4 2009,Wed, 4 Mar 2009 at 12:00:00 AM,2009-03-04T00:00:00.000000Z,4/3/09,0h 00min,00'00\",100% in 04 days\n2009/03/05,2009-03-05,03/05/09,00:00:00.000000,12:00:00 AM,Thursday March 5 2009,Thu, 5 Mar 2009 at 12:00:00 AM,2009-03-05T00:00:00.000000Z,5/3/09,0h 00min,00'00\",100% in 05 days\n2009/03/06,2009-03-06,03/06/09,00:00:00.000000,12:00:00 AM,Friday March 6 2009,Fri, 6 Mar 2009 at 12:00:00 AM,2009-03-06T00:00:00.000000Z,6/3/09,0h 00min,00'00\",100% in 06 days\n2009/03/09,2009-03-09,03/09/09,00:00:00.000000,12:00:00 AM,Monday March 9 2009,Mon, 9 Mar 2009 at 12:00:00 AM,2009-03-09T00:00:00.000000Z,9/3/09,0h 00min,00'00\",100% in 09 days\n2009/03/14,2009-03-14,03/14/09,00:00:00.000000,12:00:00 AM,Saturday March 14 2009,Sat, 14 Mar 2009 at 12:00:00 AM,2009-03-14T00:00:00.000000Z,14/3/09,0h 00min,00'00\",100% in 14 days\n2009/03/22,2009-03-22,03/22/09,00:00:00.000000,12:00:00 AM,Sunday March 22 2009,Sun, 22 Mar 2009 at 12:00:00 AM,2009-03-22T00:00:00.000000Z,22/3/09,0h 00min,00'00\",100% in 22 days\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__distinct.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {album_id, genre_id}\\ngroup tracks.* (take 1)\\nsort tracks.*\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct.prql\n---\n1,1\n2,1\n3,1\n4,1\n5,1\n6,1\n7,1\n8,2\n9,3\n10,1\n11,4\n12,5\n13,2\n14,3\n15,3\n16,3\n17,3\n18,4\n19,3\n20,6\n21,7\n22,7\n23,7\n24,7\n25,7\n26,8\n27,8\n28,7\n29,9\n30,1\n31,1\n32,10\n33,7\n34,7\n35,3\n36,1\n37,1\n38,2\n39,4\n40,1\n41,7\n42,4\n43,1\n44,1\n45,7\n46,1\n47,7\n48,2\n49,2\n50,1\n51,2\n52,11\n53,7\n54,1\n55,1\n56,7\n57,7\n58,1\n59,1\n60,1\n61,1\n62,1\n63,1\n64,1\n65,1\n66,1\n67,1\n68,2\n69,7\n70,7\n71,7\n72,6\n73,6\n73,7\n74,4\n75,4\n76,1\n77,4\n78,7\n79,1\n80,1\n81,4\n82,1\n83,12\n84,7\n85,10\n86,7\n87,2\n88,3\n89,4\n90,1\n91,1\n92,3\n93,2\n94,1\n95,3\n96,3\n97,1\n98,13\n99,1\n100,6\n101,13\n102,3\n102,13\n103,1\n104,1\n105,3\n106,3\n107,3\n108,3\n109,1\n109,3\n110,3\n111,3\n112,1\n112,3\n113,1\n114,1\n115,14\n116,1\n117,14\n118,15\n119,4\n120,1\n121,1\n122,7\n123,7\n124,16\n125,3\n126,1\n127,1\n128,1\n129,1\n130,1\n131,1\n132,1\n133,1\n134,1\n135,1\n136,1\n137,1\n138,1\n139,7\n140,7\n141,1\n141,3\n141,8\n142,7\n143,7\n144,1\n145,7\n146,14\n147,1\n148,3\n149,3\n150,3\n151,3\n152,3\n153,3\n154,3\n155,3\n156,3\n157,2\n158,7\n159,7\n160,3\n161,16\n162,3\n163,1\n164,1\n165,1\n166,7\n167,7\n168,7\n169,7\n170,1\n171,1\n172,1\n173,1\n174,3\n175,1\n176,10\n177,1\n178,1\n179,4\n180,1\n181,1\n182,1\n183,1\n184,17\n185,1\n186,1\n187,4\n188,4\n189,1\n190,4\n191,4\n192,1\n193,4\n194,1\n195,1\n196,1\n197,1\n198,1\n199,1\n200,1\n201,4\n202,4\n203,1\n204,2\n205,6\n206,1\n207,3\n208,1\n209,6\n210,6\n211,4\n212,1\n213,1\n214,1\n215,1\n216,1\n217,1\n218,1\n219,4\n220,4\n221,1\n222,7\n223,7\n224,4\n225,4\n226,18\n227,18\n227,19\n227,20\n228,19\n228,21\n229,19\n229,21\n230,19\n231,19\n231,21\n232,1\n233,1\n234,1\n235,1\n236,1\n237,1\n238,1\n239,1\n240,1\n241,8\n242,1\n243,1\n244,1\n245,1\n246,1\n247,7\n248,7\n249,19\n250,19\n251,19\n251,22\n252,1\n253,20\n254,19\n255,9\n256,1\n257,1\n258,17\n259,15\n260,23\n261,19\n261,21\n262,2\n263,16\n264,15\n265,1\n266,7\n267,2\n268,24\n269,23\n270,23\n271,23\n272,24\n273,24\n274,24\n275,24\n276,24\n277,24\n278,24\n279,24\n280,24\n281,24\n282,24\n283,24\n284,24\n285,24\n286,24\n287,24\n288,24\n289,24\n290,24\n291,24\n292,24\n293,24\n294,24\n295,24\n296,24\n297,24\n298,24\n299,24\n300,24\n301,24\n302,24\n303,24\n304,24\n305,24\n306,24\n307,24\n308,24\n309,24\n310,24\n311,24\n312,24\n313,24\n314,24\n315,24\n316,24\n317,25\n318,24\n319,24\n320,24\n321,14\n322,9\n323,23\n324,24\n325,24\n326,24\n327,24\n328,24\n329,24\n330,24\n331,24\n332,24\n333,24\n334,24\n335,24\n336,24\n337,24\n338,24\n339,24\n340,24\n341,24\n342,24\n343,24\n344,24\n345,24\n346,24\n347,10\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__distinct_on.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nselect {genre_id, media_type_id, album_id}\\ngroup {genre_id, media_type_id} (sort {-album_id} | take 1)\\nsort {-genre_id, media_type_id}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/distinct_on.prql\n---\n25,2,317\n24,2,346\n24,4,342\n24,5,268\n23,2,323\n23,3,271\n23,4,260\n22,3,251\n21,3,261\n20,3,253\n19,3,261\n18,3,227\n17,1,258\n16,1,161\n16,5,263\n15,1,259\n15,5,264\n14,1,146\n14,2,321\n13,1,102\n12,1,83\n11,1,52\n10,1,176\n10,2,347\n9,1,29\n9,2,322\n8,1,241\n7,1,248\n7,5,266\n6,1,210\n5,1,12\n4,1,225\n3,1,207\n2,1,204\n2,5,267\n1,1,246\n1,2,257\n1,5,265\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__genre_counts.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (ClickHouse prefers aliases to column names https://github.com/PRQL/prql/issues/2827)\\n# mssql:test\\nlet genre_count = (\\n    from genres\\n    aggregate {a = count name}\\n)\\n\\nfrom genre_count\\nfilter a > 0\\nselect a = -a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/genre_counts.prql\n---\n-25\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__group_all.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom a=albums\\ntake 10\\njoin tracks (==album_id)\\ngroup {a.album_id, a.title} (aggregate price = (sum tracks.unit_price | math.round 2))\\nsort album_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_all.prql\n---\n1,For Those About To Rock We Salute You,9.9\n2,Balls to the Wall,0.99\n3,Restless and Wild,2.97\n4,Let There Be Rock,7.92\n5,Big Ones,14.85\n6,Jagged Little Pill,12.87\n7,Facelift,11.88\n8,Warner 25 Anos,13.86\n9,Plays Metallica By Four Cellos,7.92\n10,Audioslave,13.86\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__group_sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nderive d = album_id + 1\\ngroup d (\\n    aggregate {\\n        n1 = (track_id | sum),\\n    }\\n)\\nsort d\\ntake 10\\nselect { d1 = d, n1 }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort.prql\n---\n2,91\n3,2\n4,12\n5,148\n6,450\n7,572\n8,678\n9,973\n10,644\n11,1281\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__group_sort_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_derive_select_join.prql\n---\n1,2,1,AC/DC\n2,2,2,Accept\n3,1,3,Aerosmith\n4,1,4,Alanis Morissette\n5,1,5,Alice In Chains\n6,2,6,Antônio Carlos Jobim\n7,1,7,Apocalyptica\n8,3,8,Audioslave\n9,1,9,BackBeat\n10,1,10,Billy Cobham\n11,2,11,Black Label Society\n12,2,12,Black Sabbath\n13,1,13,Body Count\n14,1,14,Bruce Dickinson\n15,1,15,Buddy Guy\n16,2,16,Caetano Veloso\n17,1,17,Chico Buarque\n18,2,18,Chico Science & Nação Zumbi\n19,2,19,Cidade Negra\n20,1,20,Cláudio Zoli\n21,4,21,Various Artists\n22,14,22,Led Zeppelin\n23,1,23,Frank Zappa & Captain Beefheart\n24,1,24,Marcos Valle\n27,3,27,Gilberto Gil\n36,1,36,O Rappa\n37,1,37,Ed Motta\n41,1,41,Elis Regina\n42,2,42,Milton Nascimento\n46,1,46,Jorge Ben\n50,10,50,Metallica\n51,3,51,Queen\n52,2,52,Kiss\n53,2,53,Spyro Gyra\n54,2,54,Green Day\n55,1,55,David Coverdale\n56,1,56,Gonzaguinha\n57,1,57,Os Mutantes\n58,11,58,Deep Purple\n59,3,59,Santana\n68,3,68,Miles Davis\n69,1,69,Gene Krupa\n70,1,70,Toquinho & Vinícius\n72,1,72,Vinícius De Moraes\n76,2,76,Creedence Clearwater Revival\n77,2,77,Cássia Eller\n78,1,78,Def Leppard\n79,1,79,Dennis Chambers\n80,2,80,Djavan\n81,2,81,Eric Clapton\n82,4,82,Faith No More\n83,1,83,Falamansa\n84,4,84,Foo Fighters\n85,1,85,Frank Sinatra\n86,1,86,Funk Como Le Gusta\n87,1,87,Godsmack\n88,3,88,Guns N' Roses\n89,1,89,Incognito\n90,21,90,Iron Maiden\n91,1,91,James Brown\n92,3,92,Jamiroquai\n93,1,93,JET\n94,1,94,Jimi Hendrix\n95,1,95,Joe Satriani\n96,1,96,Jota Quest\n97,1,97,João Suplicy\n98,1,98,Judas Priest\n99,2,99,Legião Urbana\n100,1,100,Lenny Kravitz\n101,2,101,Lulu Santos\n102,1,102,Marillion\n103,1,103,Marisa Monte\n104,1,104,Marvin Gaye\n105,1,105,Men At Work\n106,1,106,Motörhead\n108,1,108,Mônica Marianno\n109,1,109,Mötley Crüe\n110,2,110,Nirvana\n111,1,111,O Terço\n112,1,112,Olodum\n113,3,113,Os Paralamas Do Sucesso\n114,6,114,Ozzy Osbourne\n115,1,115,Page & Plant\n116,1,116,Passengers\n117,1,117,Paul D'Ianno\n118,5,118,Pearl Jam\n120,1,120,Pink Floyd\n121,1,121,Planet Hemp\n122,1,122,R.E.M. Feat. Kate Pearson\n124,3,124,R.E.M.\n125,1,125,Raimundos\n126,1,126,Raul Seixas\n127,3,127,Red Hot Chili Peppers\n128,1,128,Rush\n130,2,130,Skank\n131,2,131,Smashing Pumpkins\n132,1,132,Soundgarden\n133,1,133,Stevie Ray Vaughan & Double Trouble\n134,1,134,Stone Temple Pilots\n135,1,135,System Of A Down\n136,1,136,Terry Bozzio, Tony Levin & Steve Stevens\n137,2,137,The Black Crowes\n138,1,138,The Clash\n139,2,139,The Cult\n140,1,140,The Doors\n141,1,141,The Police\n142,3,142,The Rolling Stones\n143,2,143,The Tea Party\n144,1,144,The Who\n145,2,145,Tim Maia\n146,2,146,Titãs\n147,2,147,Battlestar Galactica\n148,1,148,Heroes\n149,4,149,Lost\n150,10,150,U2\n151,1,151,UB40\n152,4,152,Van Halen\n153,1,153,Velvet Revolver\n155,1,155,Zeca Pagodinho\n156,3,156,The Office\n157,1,157,Dread Zeppelin\n158,1,158,Battlestar Galactica (Classic)\n159,1,159,Aquaman\n179,1,179,Scorpions\n180,1,180,House Of Pain\n196,1,196,Cake\n197,1,197,Aisha Duo\n198,1,198,Habib Koité and Bamada\n199,1,199,Karsh Kale\n200,1,200,The Posies\n201,1,201,Luciana Souza/Romero Lubambo\n202,1,202,Aaron Goldberg\n203,1,203,Nicolaus Esterhazy Sinfonia\n204,1,204,Temple of the Dog\n205,1,205,Chris Cornell\n206,1,206,Alberto Turco & Nova Schola Gregoriana\n207,1,207,Richard Marlow & The Choir of Trinity College, Cambridge\n208,2,208,English Concert & Trevor Pinnock\n209,1,209,Anne-Sophie Mutter, Herbert Von Karajan & Wiener Philharmoniker\n210,1,210,Hilary Hahn, Jeffrey Kahane, Los Angeles Chamber Orchestra & Margaret Batjer\n211,1,211,Wilhelm Kempff\n212,1,212,Yo-Yo Ma\n213,1,213,Scholars Baroque Ensemble\n214,1,214,Academy of St. Martin in the Fields & Sir Neville Marriner\n215,1,215,Academy of St. Martin in the Fields Chamber Ensemble & Sir Neville Marriner\n216,1,216,Berliner Philharmoniker, Claudio Abbado & Sabine Meyer\n217,1,217,Royal Philharmonic Orchestra & Sir Thomas Beecham\n218,1,218,Orchestre Révolutionnaire et Romantique & John Eliot Gardiner\n219,1,219,Britten Sinfonia, Ivor Bolton & Lesley Garrett\n220,1,220,Chicago Symphony Chorus, Chicago Symphony Orchestra & Sir Georg Solti\n221,1,221,Sir Georg Solti & Wiener Philharmoniker\n222,1,222,Academy of St. Martin in the Fields, John Birch, Sir Neville Marriner & Sylvia McNair\n223,1,223,London Symphony Orchestra & Sir Charles Mackerras\n224,1,224,Barry Wordsworth & BBC Concert Orchestra\n225,1,225,Herbert Von Karajan, Mirella Freni & Wiener Philharmoniker\n226,3,226,Eugene Ormandy\n227,1,227,Luciano Pavarotti\n228,1,228,Leonard Bernstein & New York Philharmonic\n229,1,229,Boston Symphony Orchestra & Seiji Ozawa\n230,1,230,Aaron Copland & London Symphony Orchestra\n231,1,231,Ton Koopman\n232,1,232,Sergei Prokofiev & Yuri Temirkanov\n233,1,233,Chicago Symphony Orchestra & Fritz Reiner\n234,1,234,Orchestra of The Age of Enlightenment\n235,1,235,Emanuel Ax, Eugene Ormandy & Philadelphia Orchestra\n236,1,236,James Levine\n237,1,237,Berliner Philharmoniker & Hans Rosbaud\n238,1,238,Maurizio Pollini\n240,1,240,Gustav Mahler\n241,1,241,Felix Schmidt, London Symphony Orchestra & Rafael Frühbeck de Burgos\n242,1,242,Edo de Waart & San Francisco Symphony\n243,1,243,Antal Doráti & London Symphony Orchestra\n244,1,244,Choir Of Westminster Abbey & Simon Preston\n245,2,245,Michael Tilson Thomas & San Francisco Symphony\n246,1,246,Chor der Wiener Staatsoper, Herbert Von Karajan & Wiener Philharmoniker\n247,1,247,The King's Singers\n248,3,248,Berliner Philharmoniker & Herbert Von Karajan\n249,1,249,Sir Georg Solti, Sumi Jo & Wiener Philharmoniker\n250,1,250,Christopher O'Riley\n251,1,251,Fretwork\n252,2,252,Amy Winehouse\n253,1,253,Calexico\n254,1,254,Otto Klemperer & Philharmonia Orchestra\n255,1,255,Yehudi Menuhin\n256,1,256,Philharmonia Orchestra & Sir Neville Marriner\n257,1,257,Academy of St. Martin in the Fields, Sir Neville Marriner & Thurston Dart\n258,1,258,Les Arts Florissants & William Christie\n259,1,259,The 12 Cellists of The Berlin Philharmonic\n260,1,260,Adrian Leaper & Doreen de Feis\n261,1,261,Roger Norrington, London Classical Players\n262,1,262,Charles Dutoit & L'Orchestre Symphonique de Montréal\n263,1,263,Equale Brass Ensemble, John Eliot Gardiner & Munich Monteverdi Orchestra and Choir\n264,1,264,Kent Nagano and Orchestre de l'Opéra de Lyon\n265,1,265,Julian Bream\n266,1,266,Martin Roscoe\n267,1,267,Göteborgs Symfoniker & Neeme Järvi\n268,1,268,Itzhak Perlman\n269,1,269,Michele Campanella\n270,1,270,Gerald Moore\n271,1,271,Mela Tenenbaum, Pro Musica Prague & Richard Kapp\n272,1,272,Emerson String Quartet\n273,1,273,C. Monteverdi, Nigel Rogers - Chiaroscuro; London Baroque; London Cornett & Sackbu\n274,1,274,Nash Ensemble\n275,1,275,Philip Glass Ensemble\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__group_sort_filter_derive_select_join.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"s\\\"SELECT album_id,title,artist_id FROM albums\\\"\\ngroup {artist_id} (aggregate { album_title_count = count this.`title`})\\nsort {this.artist_id, this.album_title_count}\\nfilter (this.album_title_count) > 10\\nderive {new_album_count = this.album_title_count}\\nselect {this.artist_id, this.new_album_count}\\njoin side:left ( s\\\"SELECT artist_id,name as artist_name FROM artists\\\" ) (this.artist_id == that.artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_filter_derive_select_join.prql\n---\n22,14,22,Led Zeppelin\n58,11,58,Deep Purple\n90,21,90,Iron Maiden\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__group_sort_limit_take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# Compute the 3 longest songs for each genre and sort by genre\\n# mssql:test\\nfrom tracks\\nselect {genre_id,milliseconds}\\ngroup {genre_id} (\\n  sort {-milliseconds}\\n  take 3\\n)\\njoin genres (==genre_id)\\nselect {name, milliseconds}\\nsort {+name,-milliseconds}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/group_sort_limit_take.prql\n---\nAlternative,672773\nAlternative,414474\nAlternative,384497\nAlternative & Punk,558602\nAlternative & Punk,548336\nAlternative & Punk,518556\nBlues,589531\nBlues,528692\nBlues,505521\nBossa Nova,409965\nBossa Nova,392437\nBossa Nova,244297\nClassical,596519\nClassical,582029\nClassical,567494\nComedy,2541875\nComedy,2519436\nComedy,1814855\nDrama,5088838\nDrama,2780416\nDrama,2698791\nEasy Listening,292075\nEasy Listening,275879\nEasy Listening,266605\nElectronica/Dance,529684\nElectronica/Dance,422321\nElectronica/Dance,385697\nHeavy Metal,516649\nHeavy Metal,508107\nHeavy Metal,441782\nHip Hop/Rap,410409\nHip Hop/Rap,315637\nHip Hop/Rap,239908\nJazz,907520\nJazz,843964\nJazz,807392\nLatin,543007\nLatin,526132\nLatin,482429\nMetal,816509\nMetal,789472\nMetal,671712\nOpera,174813\nPop,663426\nPop,409906\nPop,315960\nR&B/Soul,418293\nR&B/Soul,341629\nR&B/Soul,340218\nReggae,366733\nReggae,353671\nReggae,341498\nRock,1612329\nRock,1196094\nRock,1116734\nRock And Roll,163265\nRock And Roll,161123\nRock And Roll,147591\nSci Fi & Fantasy,2960293\nSci Fi & Fantasy,2956998\nSci Fi & Fantasy,2956081\nScience Fiction,2713755\nScience Fiction,2627961\nScience Fiction,2626376\nSoundtrack,383764\nSoundtrack,340767\nSoundtrack,330266\nTV Shows,5286953\nTV Shows,2825166\nTV Shows,2782333\nWorld,300605\nWorld,285837\nWorld,284107\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__invoice_totals.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (clickhouse doesn't have lag function)\\n\\n#! Calculate a number of metrics about the sales of tracks in each city.\\nfrom i=invoices\\njoin ii=invoice_items (==invoice_id)\\nderive {\\n    city = i.billing_city,\\n    street = i.billing_address,\\n}\\ngroup {city, street} (\\n    derive total = ii.unit_price * ii.quantity\\n    aggregate {\\n        num_orders = count_distinct i.invoice_id,\\n        num_tracks = sum ii.quantity,\\n        total_price = sum total,\\n    }\\n)\\ngroup {city} (\\n    sort street\\n    window expanding:true (\\n        derive {running_total_num_tracks = sum num_tracks}\\n    )\\n)\\nsort {city, street}\\nderive {num_tracks_last_week = lag 7 num_tracks}\\nselect {\\n    city,\\n    street,\\n    num_orders,\\n    num_tracks,\\n    running_total_num_tracks,\\n    num_tracks_last_week\\n}\\ntake 20\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/invoice_totals.prql\n---\nAmsterdam,Lijnbaansgracht 120bg,7,38,38,\nBangalore,3,Raj Bhavan Road,6,36,36,\nBerlin,Barbarossastraße 19,7,38,38,\nBerlin,Tauentzienstraße 8,7,38,76,\nBordeaux,9, Place Louis Barthou,7,38,38,\nBoston,69 Salem Street,7,38,38,\nBrasília,Qe 7 Bloco G,7,38,38,\nBrussels,Grétrystraat 63,7,38,38,38\nBudapest,Erzsébet krt. 58.,7,38,38,36\nBuenos Aires,307 Macacha Güemes,7,38,38,38\nChicago,162 E Superior Street,7,38,38,38\nCopenhagen,Sønder Boulevard 51,7,38,38,38\nCupertino,1 Infinite Loop,7,38,38,38\nDelhi,12,Community Centre,7,38,38,38\nDijon,68, Rue Jouvence,7,38,38,38\nDublin,3 Chatham Street,7,38,38,38\nEdinburgh ,110 Raeburn Pl,7,38,38,38\nEdmonton,8210 111 ST NW,7,38,38,38\nFort Worth,2211 W Berry Street,7,38,38,38\nFrankfurt,Berger Straße 10,7,38,38,38\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__loop_01.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# clickhouse:skip (DB::Exception: Syntax error)\\n# glaredb:skip (DataFusion does not support recursive CTEs https://github.com/apache/arrow-datafusion/issues/462)\\nfrom [{n = 1}]\\nselect n = n - 2\\nloop (filter n < 4 | select n = n + 1)\\nselect n = n * 2\\nsort n\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/loop_01.prql\n---\n-2\n0\n2\n4\n6\n8\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__math_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# sqlite:skip (see https://github.com/rusqlite/rusqlite/issues/1211)\\nfrom invoices\\ntake 5\\nselect {\\n    total_original = (total | math.round 2),\\n    total_x = (math.pi - total | math.round 2 | math.abs),\\n    total_floor = (math.floor total),\\n    total_ceil = (math.ceil total),\\n    total_log10 = (math.log10 total | math.round 3),\\n    total_log2 = (math.log 2 total | math.round 3),\\n    total_sqrt = (math.sqrt total | math.round 3),\\n    total_ln = (math.ln total | math.exp | math.round 2),\\n    total_cos = (math.cos total | math.acos | math.round 2),\\n    total_sin = (math.sin total | math.asin | math.round 2),\\n    total_tan = (math.tan total | math.atan | math.round 2),\\n    total_deg = (total | math.degrees | math.radians | math.round 2),\\n    total_square = (total | math.pow 2 | math.round 2),\\n    total_square_op = ((total ** 2) | math.round 2),\\n}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/math_module.prql\n---\n1.98,1.16,1,2,0.297,0.986,1.407,1.98,1.98,1.16,-1.16,1.98,3.92,3.92\n3.96,0.82,3,4,0.598,1.986,1.99,3.96,2.32,-0.82,0.82,3.96,15.68,15.68\n5.94,2.8,5,6,0.774,2.57,2.437,5.94,0.34,-0.34,-0.34,5.94,35.28,35.28\n8.91,5.77,8,9,0.95,3.155,2.985,8.91,2.63,0.51,-0.51,8.91,79.39,79.39\n13.86,10.72,13,14,1.142,3.793,3.723,13.86,1.29,1.29,1.29,13.86,192.1,192.1\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__pipelines.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip (Only works on Sqlite implementations which have the extension\\n# installed\\n# https://stackoverflow.com/questions/24037982/how-to-use-regexp-in-sqlite)\\n\\nfrom tracks\\n\\nfilter (name ~= \\\"Love\\\")\\nfilter ((milliseconds / 1000 / 60) | in 3..4)\\nsort track_id\\ntake 1..15\\nselect {name, composer}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/pipelines.prql\n---\nMy Love,Jauperi/Zeu Góes\nThe Girl I Love She Got Long Black Wavy Hair,Jimmy Page/John Bonham/John Estes/John Paul Jones/Robert Plant\nLove Gun,Paul Stanley\nDo You Love Me,Paul Stanley, B. Ezrin, K. Fowley\nCalling Dr. Love,Gene Simmons\nUm Love,\nLove Child,Bolin/Coverdale\nLove Conquers All,Blackmore, Glover, Turner\nYou Can't Do it Right (With the One You Love),D.Coverdale/G.Hughes/Glenn Hughes/R.Blackmore/Ritchie Blackmore\nShe Loves Me Not,Bill Gould/Mike Bordin/Mike Patton\nUnderwater Love,Faith No More\nLoves Been Good To Me,rod mckuen\nLove Or Confusion,Jimi Hendrix\nMay This Be Love,Jimi Hendrix\nDo You Love Me,Paul Stanley, Bob Ezrin, Kim Fowley\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__read_csv.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# sqlite:skip\\n# postgres:skip\\n# mysql:skip\\nfrom (read_csv \\\"data_file_root/media_types.csv\\\")\\nappend (read_json \\\"data_file_root/media_types.json\\\")\\nsort media_type_id\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/read_csv.prql\n---\n1,MPEG audio file\n1,MPEG audio file\n2,Protected AAC audio file\n2,Protected AAC audio file\n3,Protected MPEG-4 video file\n3,Protected MPEG-4 video file\n4,Purchased AAC audio file\n4,Purchased AAC audio file\n5,AAC audio file\n5,AAC audio file\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__set_ops_remove.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nlet distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\\n\\nfrom_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2], [2], [3]] }'\\ndistinct\\nremove (from_text format:json '{ \\\"columns\\\": [\\\"a\\\"], \\\"data\\\": [[1], [2]] }')\\nsort a\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/set_ops_remove.prql\n---\n3\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__sort.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom e=employees\\nfilter first_name != \\\"Mitchell\\\"\\nsort {first_name, last_name}\\n\\n# joining may use HashMerge, which can undo ORDER BY\\njoin manager=employees side:left (e.reports_to == manager.employee_id)\\n\\nselect {e.first_name, e.last_name, manager.first_name}\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort.prql\n---\nAndrew,Adams,Michael\nJane,Peacock,Nancy\nLaura,Callahan,Michael\nMargaret,Park,Nancy\nMichael,Mitchell,Andrew\nNancy,Edwards,Andrew\nRobert,King,Michael\nSteve,Johnson,Nancy\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__sort_2.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from albums\\nselect { AA=album_id, artist_id }\\nsort AA\\nfilter AA >= 25\\njoin artists (==artist_id)\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_2.prql\n---\n25,18,18,Chico Science & Nação Zumbi\n26,19,19,Cidade Negra\n27,19,19,Cidade Negra\n28,20,20,Cláudio Zoli\n29,21,21,Various Artists\n30,22,22,Led Zeppelin\n31,23,23,Frank Zappa & Captain Beefheart\n32,21,21,Various Artists\n33,24,24,Marcos Valle\n34,6,6,Antônio Carlos Jobim\n35,50,50,Metallica\n36,51,51,Queen\n37,52,52,Kiss\n38,53,53,Spyro Gyra\n39,54,54,Green Day\n40,55,55,David Coverdale\n41,56,56,Gonzaguinha\n42,57,57,Os Mutantes\n43,58,58,Deep Purple\n44,22,22,Led Zeppelin\n45,21,21,Various Artists\n46,59,59,Santana\n47,37,37,Ed Motta\n48,68,68,Miles Davis\n49,68,68,Miles Davis\n50,58,58,Deep Purple\n51,69,69,Gene Krupa\n52,70,70,Toquinho & Vinícius\n53,21,21,Various Artists\n54,76,76,Creedence Clearwater Revival\n55,76,76,Creedence Clearwater Revival\n56,77,77,Cássia Eller\n57,77,77,Cássia Eller\n58,58,58,Deep Purple\n59,58,58,Deep Purple\n60,58,58,Deep Purple\n61,58,58,Deep Purple\n62,58,58,Deep Purple\n63,58,58,Deep Purple\n64,58,58,Deep Purple\n65,58,58,Deep Purple\n66,58,58,Deep Purple\n67,78,78,Def Leppard\n68,79,79,Dennis Chambers\n69,80,80,Djavan\n70,80,80,Djavan\n71,41,41,Elis Regina\n72,81,81,Eric Clapton\n73,81,81,Eric Clapton\n74,82,82,Faith No More\n75,82,82,Faith No More\n76,82,82,Faith No More\n77,82,82,Faith No More\n78,83,83,Falamansa\n79,84,84,Foo Fighters\n80,84,84,Foo Fighters\n81,84,84,Foo Fighters\n82,84,84,Foo Fighters\n83,85,85,Frank Sinatra\n84,86,86,Funk Como Le Gusta\n85,27,27,Gilberto Gil\n86,27,27,Gilberto Gil\n87,27,27,Gilberto Gil\n88,87,87,Godsmack\n89,54,54,Green Day\n90,88,88,Guns N' Roses\n91,88,88,Guns N' Roses\n92,88,88,Guns N' Roses\n93,89,89,Incognito\n94,90,90,Iron Maiden\n95,90,90,Iron Maiden\n96,90,90,Iron Maiden\n97,90,90,Iron Maiden\n98,90,90,Iron Maiden\n99,90,90,Iron Maiden\n100,90,90,Iron Maiden\n101,90,90,Iron Maiden\n102,90,90,Iron Maiden\n103,90,90,Iron Maiden\n104,90,90,Iron Maiden\n105,90,90,Iron Maiden\n106,90,90,Iron Maiden\n107,90,90,Iron Maiden\n108,90,90,Iron Maiden\n109,90,90,Iron Maiden\n110,90,90,Iron Maiden\n111,90,90,Iron Maiden\n112,90,90,Iron Maiden\n113,90,90,Iron Maiden\n114,90,90,Iron Maiden\n115,91,91,James Brown\n116,92,92,Jamiroquai\n117,92,92,Jamiroquai\n118,92,92,Jamiroquai\n119,93,93,JET\n120,94,94,Jimi Hendrix\n121,95,95,Joe Satriani\n122,46,46,Jorge Ben\n123,96,96,Jota Quest\n124,97,97,João Suplicy\n125,98,98,Judas Priest\n126,52,52,Kiss\n127,22,22,Led Zeppelin\n128,22,22,Led Zeppelin\n129,22,22,Led Zeppelin\n130,22,22,Led Zeppelin\n131,22,22,Led Zeppelin\n132,22,22,Led Zeppelin\n133,22,22,Led Zeppelin\n134,22,22,Led Zeppelin\n135,22,22,Led Zeppelin\n136,22,22,Led Zeppelin\n137,22,22,Led Zeppelin\n138,22,22,Led Zeppelin\n139,99,99,Legião Urbana\n140,99,99,Legião Urbana\n141,100,100,Lenny Kravitz\n142,101,101,Lulu Santos\n143,101,101,Lulu Santos\n144,102,102,Marillion\n145,103,103,Marisa Monte\n146,104,104,Marvin Gaye\n147,105,105,Men At Work\n148,50,50,Metallica\n149,50,50,Metallica\n150,50,50,Metallica\n151,50,50,Metallica\n152,50,50,Metallica\n153,50,50,Metallica\n154,50,50,Metallica\n155,50,50,Metallica\n156,50,50,Metallica\n157,68,68,Miles Davis\n158,42,42,Milton Nascimento\n159,42,42,Milton Nascimento\n160,106,106,Motörhead\n161,108,108,Mônica Marianno\n162,109,109,Mötley Crüe\n163,110,110,Nirvana\n164,110,110,Nirvana\n165,111,111,O Terço\n166,112,112,Olodum\n167,113,113,Os Paralamas Do Sucesso\n168,113,113,Os Paralamas Do Sucesso\n169,113,113,Os Paralamas Do Sucesso\n170,114,114,Ozzy Osbourne\n171,114,114,Ozzy Osbourne\n172,114,114,Ozzy Osbourne\n173,114,114,Ozzy Osbourne\n174,114,114,Ozzy Osbourne\n175,115,115,Page & Plant\n176,116,116,Passengers\n177,117,117,Paul D'Ianno\n178,118,118,Pearl Jam\n179,118,118,Pearl Jam\n180,118,118,Pearl Jam\n181,118,118,Pearl Jam\n182,118,118,Pearl Jam\n183,120,120,Pink Floyd\n184,121,121,Planet Hemp\n185,51,51,Queen\n186,51,51,Queen\n187,122,122,R.E.M. Feat. Kate Pearson\n188,124,124,R.E.M.\n189,124,124,R.E.M.\n190,124,124,R.E.M.\n191,125,125,Raimundos\n192,126,126,Raul Seixas\n193,127,127,Red Hot Chili Peppers\n194,127,127,Red Hot Chili Peppers\n195,127,127,Red Hot Chili Peppers\n196,128,128,Rush\n197,59,59,Santana\n198,59,59,Santana\n199,130,130,Skank\n200,130,130,Skank\n201,131,131,Smashing Pumpkins\n202,131,131,Smashing Pumpkins\n203,132,132,Soundgarden\n204,53,53,Spyro Gyra\n205,133,133,Stevie Ray Vaughan & Double Trouble\n206,134,134,Stone Temple Pilots\n207,135,135,System Of A Down\n208,136,136,Terry Bozzio, Tony Levin & Steve Stevens\n209,137,137,The Black Crowes\n210,137,137,The Black Crowes\n211,138,138,The Clash\n212,139,139,The Cult\n213,139,139,The Cult\n214,140,140,The Doors\n215,141,141,The Police\n216,142,142,The Rolling Stones\n217,142,142,The Rolling Stones\n218,142,142,The Rolling Stones\n219,143,143,The Tea Party\n220,143,143,The Tea Party\n221,144,144,The Who\n222,145,145,Tim Maia\n223,145,145,Tim Maia\n224,146,146,Titãs\n225,146,146,Titãs\n226,147,147,Battlestar Galactica\n227,147,147,Battlestar Galactica\n228,148,148,Heroes\n229,149,149,Lost\n230,149,149,Lost\n231,149,149,Lost\n232,150,150,U2\n233,150,150,U2\n234,150,150,U2\n235,150,150,U2\n236,150,150,U2\n237,150,150,U2\n238,150,150,U2\n239,150,150,U2\n240,150,150,U2\n241,151,151,UB40\n242,152,152,Van Halen\n243,152,152,Van Halen\n244,152,152,Van Halen\n245,152,152,Van Halen\n246,153,153,Velvet Revolver\n247,72,72,Vinícius De Moraes\n248,155,155,Zeca Pagodinho\n249,156,156,The Office\n250,156,156,The Office\n251,156,156,The Office\n252,157,157,Dread Zeppelin\n253,158,158,Battlestar Galactica (Classic)\n254,159,159,Aquaman\n255,150,150,U2\n256,114,114,Ozzy Osbourne\n257,179,179,Scorpions\n258,180,180,House Of Pain\n259,36,36,O Rappa\n260,196,196,Cake\n261,149,149,Lost\n262,197,197,Aisha Duo\n263,198,198,Habib Koité and Bamada\n264,199,199,Karsh Kale\n265,200,200,The Posies\n266,201,201,Luciana Souza/Romero Lubambo\n267,202,202,Aaron Goldberg\n268,203,203,Nicolaus Esterhazy Sinfonia\n269,204,204,Temple of the Dog\n270,205,205,Chris Cornell\n271,8,8,Audioslave\n272,206,206,Alberto Turco & Nova Schola Gregoriana\n273,207,207,Richard Marlow & The Choir of Trinity College, Cambridge\n274,208,208,English Concert & Trevor Pinnock\n275,209,209,Anne-Sophie Mutter, Herbert Von Karajan & Wiener Philharmoniker\n276,210,210,Hilary Hahn, Jeffrey Kahane, Los Angeles Chamber Orchestra & Margaret Batjer\n277,211,211,Wilhelm Kempff\n278,212,212,Yo-Yo Ma\n279,213,213,Scholars Baroque Ensemble\n280,214,214,Academy of St. Martin in the Fields & Sir Neville Marriner\n281,215,215,Academy of St. Martin in the Fields Chamber Ensemble & Sir Neville Marriner\n282,216,216,Berliner Philharmoniker, Claudio Abbado & Sabine Meyer\n283,217,217,Royal Philharmonic Orchestra & Sir Thomas Beecham\n284,218,218,Orchestre Révolutionnaire et Romantique & John Eliot Gardiner\n285,219,219,Britten Sinfonia, Ivor Bolton & Lesley Garrett\n286,220,220,Chicago Symphony Chorus, Chicago Symphony Orchestra & Sir Georg Solti\n287,221,221,Sir Georg Solti & Wiener Philharmoniker\n288,222,222,Academy of St. Martin in the Fields, John Birch, Sir Neville Marriner & Sylvia McNair\n289,223,223,London Symphony Orchestra & Sir Charles Mackerras\n290,224,224,Barry Wordsworth & BBC Concert Orchestra\n291,225,225,Herbert Von Karajan, Mirella Freni & Wiener Philharmoniker\n292,226,226,Eugene Ormandy\n293,227,227,Luciano Pavarotti\n294,228,228,Leonard Bernstein & New York Philharmonic\n295,229,229,Boston Symphony Orchestra & Seiji Ozawa\n296,230,230,Aaron Copland & London Symphony Orchestra\n297,231,231,Ton Koopman\n298,232,232,Sergei Prokofiev & Yuri Temirkanov\n299,233,233,Chicago Symphony Orchestra & Fritz Reiner\n300,234,234,Orchestra of The Age of Enlightenment\n301,235,235,Emanuel Ax, Eugene Ormandy & Philadelphia Orchestra\n302,236,236,James Levine\n303,237,237,Berliner Philharmoniker & Hans Rosbaud\n304,238,238,Maurizio Pollini\n305,240,240,Gustav Mahler\n306,241,241,Felix Schmidt, London Symphony Orchestra & Rafael Frühbeck de Burgos\n307,242,242,Edo de Waart & San Francisco Symphony\n308,243,243,Antal Doráti & London Symphony Orchestra\n309,244,244,Choir Of Westminster Abbey & Simon Preston\n310,245,245,Michael Tilson Thomas & San Francisco Symphony\n311,226,226,Eugene Ormandy\n312,245,245,Michael Tilson Thomas & San Francisco Symphony\n313,246,246,Chor der Wiener Staatsoper, Herbert Von Karajan & Wiener Philharmoniker\n314,247,247,The King's Singers\n315,208,208,English Concert & Trevor Pinnock\n316,248,248,Berliner Philharmoniker & Herbert Von Karajan\n317,249,249,Sir Georg Solti, Sumi Jo & Wiener Philharmoniker\n318,250,250,Christopher O'Riley\n319,251,251,Fretwork\n320,248,248,Berliner Philharmoniker & Herbert Von Karajan\n321,252,252,Amy Winehouse\n322,252,252,Amy Winehouse\n323,253,253,Calexico\n324,254,254,Otto Klemperer & Philharmonia Orchestra\n325,255,255,Yehudi Menuhin\n326,256,256,Philharmonia Orchestra & Sir Neville Marriner\n327,257,257,Academy of St. Martin in the Fields, Sir Neville Marriner & Thurston Dart\n328,258,258,Les Arts Florissants & William Christie\n329,259,259,The 12 Cellists of The Berlin Philharmonic\n330,260,260,Adrian Leaper & Doreen de Feis\n331,261,261,Roger Norrington, London Classical Players\n332,262,262,Charles Dutoit & L'Orchestre Symphonique de Montréal\n333,263,263,Equale Brass Ensemble, John Eliot Gardiner & Munich Monteverdi Orchestra and Choir\n334,264,264,Kent Nagano and Orchestre de l'Opéra de Lyon\n335,265,265,Julian Bream\n336,248,248,Berliner Philharmoniker & Herbert Von Karajan\n337,266,266,Martin Roscoe\n338,267,267,Göteborgs Symfoniker & Neeme Järvi\n339,268,268,Itzhak Perlman\n340,269,269,Michele Campanella\n341,270,270,Gerald Moore\n342,271,271,Mela Tenenbaum, Pro Musica Prague & Richard Kapp\n343,226,226,Eugene Ormandy\n344,272,272,Emerson String Quartet\n345,273,273,C. Monteverdi, Nigel Rogers - Chiaroscuro; London Baroque; London Cornett & Sackbu\n346,274,274,Nash Ensemble\n347,275,275,Philip Glass Ensemble\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__sort_3.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"from [{track_id=0, album_id=1, genre_id=2}]\\nselect { AA=track_id, album_id, genre_id }\\nsort AA\\njoin side:left [{album_id=1, album_title=\\\"Songs\\\"}] (==album_id)\\nselect { AA, AT = album_title ?? \\\"unknown\\\", genre_id }\\nfilter AA < 25\\njoin side:left [{genre_id=1, genre_title=\\\"Rock\\\"}] (==genre_id)\\nselect { AA, AT, GT = genre_title ?? \\\"unknown\\\" }\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/sort_3.prql\n---\n0,Songs,unknown\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__switch.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# glaredb:skip (May be a bag of String type conversion for Postgres Client)\\n# mssql:test\\nfrom tracks\\nsort milliseconds\\nselect display = case [\\n    composer != null => composer,\\n    genre_id < 17 => 'no composer',\\n    true => f'unknown composer'\\n]\\ntake 10\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/switch.prql\n---\nSamuel Rosa\nno composer\nno composer\nno composer\nL. Muggerud\nno composer\nL. Muggerud\nunknown composer\nGilberto Gil\nChico Science\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__take.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\nfrom tracks\\nsort {+track_id}\\ntake 3..5\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/take.prql\n---\n3,Fast As a Shark,3,2,1,F. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman,230619,3990994,0.99\n4,Restless and Wild,3,2,1,F. Baltes, R.A. Smith-Diesel, S. Kaufman, U. Dirkscneider & W. Hoffman,252051,4331779,0.99\n5,Princess of the Dawn,3,2,1,Deaffy & R.A. Smith-Diesel,375418,6290521,0.99\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__text_module.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:test\\n# glaredb:skip — TODO: started raising an error on 2024-05-20; see `window.prql`\\n# for more details\\nfrom albums\\nselect {\\n    title,\\n    title_and_spaces = f\\\"  {title}  \\\",\\n    low = (title | text.lower),\\n    up = (title | text.upper),\\n    ltrimmed = (title | text.ltrim),\\n    rtrimmed = (title | text.rtrim),\\n    trimmed = (title | text.trim),\\n    len = (title | text.length),\\n    subs = (title | text.extract 2 5),\\n    replace = (title | text.replace \\\"al\\\" \\\"PIKA\\\"),\\n}\\nsort {title}\\nfilter (title | text.starts_with \\\"Black\\\") || (title | text.contains \\\"Sabbath\\\") || (title | text.ends_with \\\"os\\\")\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/text_module.prql\n---\nBach: The Brandenburg Concertos,  Bach: The Brandenburg Concertos  ,bach: the brandenburg concertos,BACH: THE BRANDENBURG CONCERTOS,Bach: The Brandenburg Concertos,Bach: The Brandenburg Concertos,Bach: The Brandenburg Concertos,31,ach: ,Bach: The Brandenburg Concertos\nBach: Violin Concertos,  Bach: Violin Concertos  ,bach: violin concertos,BACH: VIOLIN CONCERTOS,Bach: Violin Concertos,Bach: Violin Concertos,Bach: Violin Concertos,22,ach: ,Bach: Violin Concertos\nBartok: Violin & Viola Concertos,  Bartok: Violin & Viola Concertos  ,bartok: violin & viola concertos,BARTOK: VIOLIN & VIOLA CONCERTOS,Bartok: Violin & Viola Concertos,Bartok: Violin & Viola Concertos,Bartok: Violin & Viola Concertos,32,artok,Bartok: Violin & Viola Concertos\nBlack Album,  Black Album  ,black album,BLACK ALBUM,Black Album,Black Album,Black Album,11,lack ,Black Album\nBlack Sabbath,  Black Sabbath  ,black sabbath,BLACK SABBATH,Black Sabbath,Black Sabbath,Black Sabbath,13,lack ,Black Sabbath\nBlack Sabbath Vol. 4 (Remaster),  Black Sabbath Vol. 4 (Remaster)  ,black sabbath vol. 4 (remaster),BLACK SABBATH VOL. 4 (REMASTER),Black Sabbath Vol. 4 (Remaster),Black Sabbath Vol. 4 (Remaster),Black Sabbath Vol. 4 (Remaster),31,lack ,Black Sabbath Vol. 4 (Remaster)\nDa Lama Ao Caos,  Da Lama Ao Caos  ,da lama ao caos,DA LAMA AO CAOS,Da Lama Ao Caos,Da Lama Ao Caos,Da Lama Ao Caos,15,a Lam,Da Lama Ao Caos\nJorge Ben Jor 25 Anos,  Jorge Ben Jor 25 Anos  ,jorge ben jor 25 anos,JORGE BEN JOR 25 ANOS,Jorge Ben Jor 25 Anos,Jorge Ben Jor 25 Anos,Jorge Ben Jor 25 Anos,21,orge ,Jorge Ben Jor 25 Anos\nMeus Momentos,  Meus Momentos  ,meus momentos,MEUS MOMENTOS,Meus Momentos,Meus Momentos,Meus Momentos,13,eus M,Meus Momentos\nMozart: Wind Concertos,  Mozart: Wind Concertos  ,mozart: wind concertos,MOZART: WIND CONCERTOS,Mozart: Wind Concertos,Mozart: Wind Concertos,Mozart: Wind Concertos,22,ozart,Mozart: Wind Concertos\nPlays Metallica By Four Cellos,  Plays Metallica By Four Cellos  ,plays metallica by four cellos,PLAYS METALLICA BY FOUR CELLOS,Plays Metallica By Four Cellos,Plays Metallica By Four Cellos,Plays Metallica By Four Cellos,30,lays ,Plays MetPIKAlica By Four Cellos\nWarner 25 Anos,  Warner 25 Anos  ,warner 25 anos,WARNER 25 ANOS,Warner 25 Anos,Warner 25 Anos,Warner 25 Anos,14,arner,Warner 25 Anos\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/snapshots/integration__queries__results__window.snap",
    "content": "---\nsource: prqlc/prqlc/tests/integration/queries.rs\nexpression: \"# mssql:skip Conversion(\\\"cannot interpret I64(Some(1)) as an i32 value\\\")', connection.rs:200:34\\n# duckdb:skip problems with DISTINCT ON (duckdb internal error: [with INPUT_TYPE = int; RESULT_TYPE = unsigned char]: Assertion `min_val <= input' failed.)\\n# clickhouse:skip problems with DISTINCT ON\\n# postgres:skip problems with DISTINCT ON\\n# glaredb:skip — TODO: started raising an error on 2024-05-20, from https://github.com/PRQL/prql/actions/runs/9154902656/job/25198160283:\\n    # ERROR: This feature is not implemented: Unsupported ast node in sqltorel:\\n    # Substring { expr: Identifier(Ident { value: \\\"title\\\", quote_style: None }),\\n    # substring_from: Some(Value(Number(\\\"2\\\", false))), substring_for:\\n    # Some(Value(Number(\\\"5\\\", false))), special: true }\\nfrom tracks\\ngroup genre_id (\\n  sort milliseconds\\n  derive {\\n    num = row_number this,\\n    total = count this,\\n    last_val = last track_id,\\n  }\\n  take 10\\n)\\nsort {genre_id, milliseconds}\\nselect {track_id, genre_id, num, total, last_val}\\nfilter genre_id >= 22\\n\"\ninput_file: prqlc/prqlc/tests/integration/queries/window.prql\n---\n3219,22,1,17,3219\n3218,22,2,17,3218\n3214,22,3,17,3214\n3210,22,4,17,3210\n3213,22,5,17,3213\n3216,22,6,17,3216\n3208,22,7,17,3208\n3211,22,8,17,3211\n3215,22,9,17,3215\n3221,22,10,17,3221\n3379,23,1,40,3379\n3384,23,2,40,3384\n3399,23,3,40,3399\n3395,23,4,40,3395\n3377,23,5,40,3377\n3478,23,6,40,3478\n3385,23,7,40,3385\n3390,23,8,40,3390\n3381,23,9,40,3381\n3393,23,10,40,3393\n3496,24,1,74,3496\n3501,24,2,74,3501\n3448,24,3,74,3448\n3452,24,4,74,3452\n3483,24,5,74,3483\n3449,24,6,74,3449\n3408,24,7,74,3408\n3447,24,8,74,3447\n3492,24,9,74,3492\n3500,24,10,74,3500\n3451,25,1,1,3451\n"
  },
  {
    "path": "prqlc/prqlc/tests/integration/sql.rs",
    "content": "//! Simple tests for \"this PRQL creates this SQL\" go here.\nuse insta::assert_snapshot;\nuse prqlc::{sql, ErrorMessages, Options, SourceTree, Target};\nuse rstest::rstest;\n\npub(crate) fn compile(prql: &str) -> Result<String, ErrorMessages> {\n    prqlc::compile(\n        prql,\n        &Options::default()\n            .no_signature()\n            .with_display(prqlc::DisplayOptions::Plain),\n    )\n}\n\nfn compile_with_sql_dialect(prql: &str, dialect: sql::Dialect) -> Result<String, ErrorMessages> {\n    prqlc::compile(\n        prql,\n        &Options::default()\n            .no_signature()\n            .with_target(Target::Sql(Some(dialect)))\n            .with_display(prqlc::DisplayOptions::Plain),\n    )\n}\n\n#[test]\nfn test_stdlib() {\n    assert_snapshot!(compile(r###\"\n    from employees\n    aggregate (\n        {salary_usd = min salary}\n    )\n    \"###).unwrap(),\n        @r\"\n    SELECT\n      MIN(salary) AS salary_usd\n    FROM\n      employees\n    \"\n    );\n\n    assert_snapshot!(compile(r###\"\n    from employees\n    aggregate (\n        {salary_usd = (math.round 2 salary)}\n    )\n    \"###).unwrap(),\n        @r\"\n    SELECT\n      ROUND(salary, 2) AS salary_usd\n    FROM\n      employees\n    \"\n    );\n}\n\n#[test]\nfn test_stdlib_math_module() {\n    assert_snapshot!(compile(r#\"\n    from employees\n    select {\n      salary_abs = math.abs salary,\n      salary_floor = math.floor salary,\n      salary_ceil = math.ceil salary,\n      salary_pi = math.pi,\n      salary_exp = math.exp salary,\n      salary_ln = math.ln salary,\n      salary_log10 = math.log10 salary,\n      salary_log = math.log 2 salary,\n      salary_sqrt = math.sqrt salary,\n      salary_degrees = math.degrees salary,\n      salary_radians = math.radians salary,\n      salary_cos = math.cos salary,\n      salary_acos = math.acos salary,\n      salary_sin = math.sin salary,\n      salary_asin = math.asin salary,\n      salary_tan = math.tan salary,\n      salary_atan = math.atan salary,\n      salary_pow = (salary | math.pow 2),\n      salary_pow_op = salary ** 2,\n    }\n    \"#).unwrap(), @r\"\n    SELECT\n      ABS(salary) AS salary_abs,\n      FLOOR(salary) AS salary_floor,\n      CEIL(salary) AS salary_ceil,\n      PI() AS salary_pi,\n      EXP(salary) AS salary_exp,\n      LN(salary) AS salary_ln,\n      LOG10(salary) AS salary_log10,\n      LOG10(salary) / LOG10(2) AS salary_log,\n      SQRT(salary) AS salary_sqrt,\n      DEGREES(salary) AS salary_degrees,\n      RADIANS(salary) AS salary_radians,\n      COS(salary) AS salary_cos,\n      ACOS(salary) AS salary_acos,\n      SIN(salary) AS salary_sin,\n      ASIN(salary) AS salary_asin,\n      TAN(salary) AS salary_tan,\n      ATAN(salary) AS salary_atan,\n      POW(salary, 2) AS salary_pow,\n      POW(salary, 2) AS salary_pow_op\n    FROM\n      employees\n    \"\n    );\n}\n\n#[test]\nfn test_stdlib_math_module_mssql() {\n    assert_snapshot!(compile(r#\"\n  prql target:sql.mssql\n\n  from employees\n  select {\n    salary_abs = math.abs salary,\n    salary_floor = math.floor salary,\n    salary_ceil = math.ceil salary,\n    salary_pi = math.pi,\n    salary_exp = math.exp salary,\n    salary_ln = math.ln salary,\n    salary_log10 = math.log10 salary,\n    salary_log = math.log 2 salary,\n    salary_sqrt = math.sqrt salary,\n    salary_degrees = math.degrees salary,\n    salary_radians = math.radians salary,\n    salary_cos = math.cos salary,\n    salary_acos = math.acos salary,\n    salary_sin = math.sin salary,\n    salary_asin = math.asin salary,\n    salary_tan = math.tan salary,\n    salary_atan = math.atan salary,\n    salary_pow = (salary | math.pow 2),\n  }\n  \"#).unwrap(), @r\"\n    SELECT\n      ABS(salary) AS salary_abs,\n      FLOOR(salary) AS salary_floor,\n      CEILING(salary) AS salary_ceil,\n      PI() AS salary_pi,\n      EXP(salary) AS salary_exp,\n      LOG(salary) AS salary_ln,\n      LOG10(salary) AS salary_log10,\n      LOG10(salary) / LOG10(2) AS salary_log,\n      SQRT(salary) AS salary_sqrt,\n      DEGREES(salary) AS salary_degrees,\n      RADIANS(salary) AS salary_radians,\n      COS(salary) AS salary_cos,\n      ACOS(salary) AS salary_acos,\n      SIN(salary) AS salary_sin,\n      ASIN(salary) AS salary_asin,\n      TAN(salary) AS salary_tan,\n      ATAN(salary) AS salary_atan,\n      POWER(salary, 2) AS salary_pow\n    FROM\n      employees\n    \"\n    );\n}\n\n#[test]\nfn test_stdlib_text_module() {\n    assert_snapshot!(compile(r#\"\n    from employees\n    select {\n      name_lower = (name | text.lower),\n      name_upper = (name | text.upper),\n      name_ltrim = (name | text.ltrim),\n      name_rtrim = (name | text.rtrim),\n      name_trim = (name | text.trim),\n      name_length = (name | text.length),\n      name_extract = (name | text.extract 3 5),\n      name_replace = (name | text.replace \"pika\" \"chu\"),\n      name_starts_with = (name | text.starts_with \"pika\"),\n      name_contains = (name | text.contains \"pika\"),\n      name_ends_with = (name | text.ends_with \"pika\"),\n    }\n    \"#).unwrap(), @r\"\n    SELECT\n      LOWER(name) AS name_lower,\n      UPPER(name) AS name_upper,\n      LTRIM(name) AS name_ltrim,\n      RTRIM(name) AS name_rtrim,\n      TRIM(name) AS name_trim,\n      CHAR_LENGTH(name) AS name_length,\n      SUBSTRING(name, 3, 5) AS name_extract,\n      REPLACE(name, 'pika', 'chu') AS name_replace,\n      name LIKE CONCAT('pika', '%') AS name_starts_with,\n      name LIKE CONCAT('%', 'pika', '%') AS name_contains,\n      name LIKE CONCAT('%', 'pika') AS name_ends_with\n    FROM\n      employees\n    \"\n    );\n}\n\n#[rstest]\n#[case::generic(sql::Dialect::Generic, \"LIKE CONCAT('%', 'pika', '%')\")]\n#[case::sqlite(sql::Dialect::SQLite, \"LIKE '%' || 'pika' || '%'\")] // `CONCAT` is not supported in SQLite\nfn like_concat(#[case] dialect: sql::Dialect, #[case] expected_like: &'static str) {\n    let query = r#\"\n  from employees\n  select {\n    name_ends_with = (name | text.contains \"pika\"),\n  }\n  \"#;\n    let expected = format!(\n        r#\"\nSELECT\n  name {expected_like} AS name_ends_with\nFROM\n  employees\n\"#\n    );\n    assert_eq!(\n        compile_with_sql_dialect(query, dialect).unwrap(),\n        expected.trim_start()\n    )\n}\n\n#[rstest]\n#[case::generic(\n    sql::Dialect::Generic,\n    r#\"\nSELECT\n  a,\n  b,\n  \"col space\"\nFROM\n  employees\n\"#\n)]\n#[case::snowflake(\n    sql::Dialect::Snowflake,\n    r#\"\nSELECT\n  \"a\",\n  \"b\",\n  \"col space\"\nFROM\n  \"employees\"\n\"#\n)]\nfn test_quoting_style(#[case] dialect: sql::Dialect, #[case] expected_sql: &'static str) {\n    let query = r#\"\n  from employees\n  select { a, `b`, `col space` }\n  \"#;\n\n    assert_eq!(\n        compile_with_sql_dialect(query, dialect).unwrap(),\n        expected_sql.trim_start()\n    )\n}\n\n#[rstest]\n#[case::clickhouse(\n    sql::Dialect::ClickHouse,\n    \"formatDateTimeInJodaSyntax(invoice_date, 'dd/MM/yyyy')\"\n)]\n#[case::duckdb(sql::Dialect::DuckDb, \"strftime(invoice_date, '%d/%m/%Y')\")]\n#[case::postgres(sql::Dialect::Postgres, \"TO_CHAR(invoice_date, 'DD/MM/YYYY')\")]\n#[case::mssql(sql::Dialect::MsSql, \"FORMAT(invoice_date, 'dd/MM/yyyy')\")]\n#[case::mysql(sql::Dialect::MySql, \"DATE_FORMAT(invoice_date, '%d/%m/%Y')\")]\n#[case::bigquery(\n    sql::Dialect::BigQuery,\n    \"FORMAT_TIMESTAMP('%d/%m/%Y', CAST(invoice_date AS TIMESTAMP))\"\n)]\nfn date_to_text_operator(\n    #[case] dialect: sql::Dialect,\n    #[case] expected_date_to_text: &'static str,\n) {\n    let query = r#\"\n    from invoices\n    select {\n      invoice_date = (invoice_date | date.to_text \"%d/%m/%Y\")\n    }\"#;\n    let expected = format!(\n        r#\"\nSELECT\n  {expected_date_to_text} AS invoice_date\nFROM\n  invoices\n\"#\n    );\n    assert_eq!(\n        compile_with_sql_dialect(query, dialect).unwrap(),\n        expected.trim_start()\n    )\n}\n\n#[test]\nfn date_to_text_bigquery_rfc3339() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.bigquery\n\n    from [{d = @2021-01-01}]\n    derive {\n      d_str = (d | date.to_text \"%+\")\n    }\"#).unwrap(), @\"\n    WITH table_0 AS (\n      SELECT\n        DATE '2021-01-01' AS d\n    )\n    SELECT\n      d,\n      FORMAT_TIMESTAMP('%Y-%m-%dT%H:%M:%S%Ez', CAST(d AS TIMESTAMP)) AS d_str\n    FROM\n      table_0\n    \");\n}\n\n#[test]\nfn json_of_test() {\n    let pl = prqlc::prql_to_pl(\"from employees | take 10\").unwrap();\n    let json = prqlc::json::from_pl(&pl).unwrap();\n\n    // Since the AST is so in flux right now just test that the brackets are present\n    assert_eq!(json.chars().next().unwrap(), '{');\n    assert_eq!(json.chars().nth(json.len() - 1).unwrap(), '}');\n}\n\n#[test]\nfn test_precedence_division() {\n    assert_snapshot!((compile(r###\"\n    from artists\n    derive {\n      p1 = a - (b + c), # needs parentheses\n      p2 = x / (y * z), # needs parentheses\n      np1 = x / y / z, # doesn't need parentheses\n      p3 = x / (y / z), # needs parentheses\n      np4 = (x / y) / z, # doesn't need parentheses\n    }\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      a - (b + c) AS p1,\n      x / (y * z) AS p2,\n      x / y / z AS np1,\n      x / (y / z) AS p3,\n      x / y / z AS np4\n    FROM\n      artists\n    \");\n}\n\n#[test]\nfn test_precedence_01() {\n    assert_snapshot!((compile(r###\"\n    from artists\n    derive {\n      p1 = a - (b + c), # needs parentheses\n      p2 = a / (b * c), # needs parentheses\n      np1 = a + (b - c), # no parentheses\n      np2 = (a + b) - c, # no parentheses\n    }\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      a - (b + c) AS p1,\n      a / (b * c) AS p2,\n      a + b - c AS np1,\n      a + b - c AS np2\n    FROM\n      artists\n    \");\n}\n\n#[test]\nfn test_precedence_02() {\n    assert_snapshot!((compile(r###\"\n    from x\n    derive {\n      temp_c = (temp_f - 32) / 1.8,\n      temp_f = temp_c * 9/5,\n      temp_z = temp_x + 9 - 5,\n    }\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      (temp_f - 32) / 1.8 AS temp_c,\n      (temp_f - 32) / 1.8 * 9 / 5 AS temp_f,\n      temp_x + 9 - 5 AS temp_z\n    FROM\n      x\n    \");\n}\n\n#[test]\nfn test_precedence_03() {\n    assert_snapshot!((compile(r###\"\n    from numbers\n    derive {\n      sum_1 = a + b,\n      sum_2 = std.add a b,\n      g = -a\n    }\n    select {\n      result = c * sum_1 + sum_2,\n      a * g\n    }\n    \"###).unwrap()), @r\"\n    SELECT\n      c * (a + b) + a + b AS result,\n      a * - a\n    FROM\n      numbers\n    \");\n}\n\n#[test]\nfn test_precedence_04() {\n    assert_snapshot!((compile(r###\"\n    from comparisons\n    select {\n      gtz = a > 0,\n      ltz = !(a > 0),\n      zero = !gtz && !ltz,\n      is_not_equal = !(a==b),\n      is_not_gt = !(a>b),\n      negated_is_null_1 = !a == null,\n      negated_is_null_2 = (!a) == null,\n      is_not_null = !(a == null),\n      (a + b) == null,\n    }\n    \"###).unwrap()), @r\"\n    SELECT\n      a > 0 AS gtz,\n      NOT a > 0 AS ltz,\n      NOT a > 0\n      AND NOT NOT a > 0 AS zero,\n      NOT a = b AS is_not_equal,\n      NOT a > b AS is_not_gt,\n      (NOT a) IS NULL AS negated_is_null_1,\n      (NOT a) IS NULL AS negated_is_null_2,\n      NOT a IS NULL AS is_not_null,\n      a + b IS NULL\n    FROM\n      comparisons\n    \");\n}\n\n#[test]\nfn test_precedence_05() {\n    assert_snapshot!(compile(\n    r###\"\n    from numbers\n    derive x = (y - z)\n    select {\n      c - (a + b),\n      c + (a - b),\n      c + a - b,\n      c + a + b,\n      (c + a) - b,\n      ((c - d) - (a - b)),\n      ((c + d) + (a - b)),\n      a / (b * c),\n      +x,\n      -x,\n    }\n    \"###\n    ).unwrap(), @r\"\n    SELECT\n      c - (a + b),\n      c + a - b,\n      c + a - b,\n      c + a + b,\n      c + a - b,\n      c - d - (a - b),\n      c + d + a - b,\n      a / (b * c),\n      y - z AS x,\n      -(y - z)\n    FROM\n      numbers\n    \"\n    );\n}\n\n#[test]\n#[ignore]\n// FIXME: right associativity of `pow` is not implemented yet\nfn test_pow_is_right_associative() {\n    assert_snapshot!(compile(r#\"\n    from numbers\n    select {\n      c ** a ** b\n    }\n    \"#).unwrap(), @r#\"\n    SELECT\n      POW(c, POW(a, b))\n    FROM\n      numbers\n    \"#\n    );\n}\n\n#[test]\nfn test_append() {\n    assert_snapshot!(compile(r###\"\n    from employees\n    append managers\n    \"###).unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    UNION\n    ALL\n    SELECT\n      *\n    FROM\n      managers\n    \");\n\n    assert_snapshot!(compile(r###\"\n    from employees\n    select {name, cost = salary}\n    take 3\n    append (\n        from employees\n        select {name, cost = salary + bonuses}\n        take 10\n    )\n    \"###).unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      (\n        SELECT\n          name,\n          salary AS cost\n        FROM\n          employees\n        LIMIT\n          3\n      ) AS table_2\n    UNION\n    ALL\n    SELECT\n      *\n    FROM\n      (\n        SELECT\n          name,\n          salary + bonuses AS cost\n        FROM\n          employees\n        LIMIT\n          10\n      ) AS table_3\n    \");\n\n    assert_snapshot!(compile(r###\"\n    let distinct = rel -> (_param.rel | group this (take 1))\n    let union = func `default_db.bottom` top -> (top | append bottom | distinct)\n\n    from employees\n    union (from managers)\n    \"###).unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    UNION\n    DISTINCT\n    SELECT\n      *\n    FROM\n      managers\n    \");\n\n    assert_snapshot!(compile(r###\"\n    let distinct = rel -> (_param.rel | group this (take 1))\n    let union = func `default_db.bottom` top -> (top | append bottom | distinct)\n\n    from employees\n    append managers\n    union all_employees_of_some_other_company\n    \"###).unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    UNION\n    ALL\n    SELECT\n      *\n    FROM\n      managers\n    UNION\n    DISTINCT\n    SELECT\n      *\n    FROM\n      all_employees_of_some_other_company\n    \");\n}\n\n#[test]\nfn test_remove_01() {\n    assert_snapshot!(compile(r#\"\n    from albums\n    remove artists\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      *\n    FROM\n      albums AS t\n    EXCEPT\n      ALL\n    SELECT\n      *\n    FROM\n      artists AS b\n    \"\n    );\n}\n\n#[test]\nfn test_remove_02() {\n    assert_snapshot!(compile(r#\"\n    from album\n    select artist_id\n    remove (\n        from artist | select artist_id\n    )\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        artist_id\n      FROM\n        artist\n    )\n    SELECT\n      artist_id\n    FROM\n      album\n    EXCEPT\n      ALL\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_remove_03() {\n    assert_snapshot!(compile(r#\"\n    from album\n    select {artist_id, title}\n    remove (\n        from artist | select artist_id\n    )\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        artist_id\n      FROM\n        artist\n    )\n    SELECT\n      album.artist_id,\n      album.title\n    FROM\n      album\n      LEFT OUTER JOIN table_0 ON album.artist_id = table_0.artist_id\n    WHERE\n      table_0.artist_id IS NULL\n    \"\n    );\n}\n\n#[test]\nfn test_remove_04() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.sqlite\n\n    from album\n    remove artist\n    \"#).unwrap_err(),\n        @r\"\n    Error: The dialect SQLiteDialect does not support EXCEPT ALL\n    ↳ Hint: providing more column information will allow the query to be translated to an anti-join.\n    \"\n    );\n}\n\n#[test]\nfn test_remove_05() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.sqlite\n\n    let distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\n    let except = `default_db.bottom` top -> (top | distinct | remove bottom)\n\n    from album\n    select {artist_id, title}\n    except (from artist | select {artist_id, name})\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        artist_id,\n        name\n      FROM\n        artist\n    )\n    SELECT\n      artist_id,\n      title\n    FROM\n      album\n    EXCEPT\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_remove_06() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.sqlite\n\n    let distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\n    let except = func `default_db.bottom` top -> (top | distinct | remove bottom)\n\n    from album\n    except artist\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      *\n    FROM\n      album AS t\n    EXCEPT\n    SELECT\n      *\n    FROM\n      artist AS b\n    \"\n    );\n}\n\n#[test]\nfn test_intersect_01() {\n    assert_snapshot!(compile(r#\"\n    from album\n    intersect artist\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      *\n    FROM\n      album AS t\n    INTERSECT\n    ALL\n    SELECT\n      *\n    FROM\n      artist AS b\n    \"\n    );\n}\n\n#[test]\nfn test_intersect_02() {\n    assert_snapshot!(compile(r#\"\n    from album\n    select artist_id\n    intersect (\n        from artist | select artist_id\n    )\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        artist_id\n      FROM\n        artist\n    )\n    SELECT\n      artist_id\n    FROM\n      album\n    INTERSECT\n    ALL\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_intersect_03() {\n    assert_snapshot!(compile(r#\"\n    let distinct = rel -> (_param.rel | group this (take 1))\n\n    from album\n    select artist_id\n    distinct\n    intersect (\n        from artist | select artist_id\n    )\n    distinct\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        artist_id\n      FROM\n        artist\n    ),\n    table_1 AS (\n      SELECT\n        artist_id\n      FROM\n        album\n      INTERSECT\n      DISTINCT\n      SELECT\n        *\n      FROM\n        table_0\n    )\n    SELECT\n      DISTINCT artist_id\n    FROM\n      table_1\n    \"\n    );\n}\n\n#[test]\nfn test_intersect_04() {\n    assert_snapshot!(compile(r#\"\n    let distinct = rel -> (_param.rel | group this (take 1))\n\n    from album\n    select artist_id\n    intersect (\n        from artist | select artist_id\n    )\n    distinct\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        artist_id\n      FROM\n        artist\n    ),\n    table_1 AS (\n      SELECT\n        artist_id\n      FROM\n        album\n      INTERSECT\n      ALL\n      SELECT\n        *\n      FROM\n        table_0\n    )\n    SELECT\n      DISTINCT artist_id\n    FROM\n      table_1\n    \"\n    );\n}\n\n#[test]\nfn test_intersect_05() {\n    assert_snapshot!(compile(r#\"\n    let distinct = rel -> (_param.rel | group this (take 1))\n\n    from album\n    select artist_id\n    distinct\n    intersect (\n        from artist | select artist_id\n    )\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        artist_id\n      FROM\n        artist\n    )\n    SELECT\n      artist_id\n    FROM\n      album\n    INTERSECT\n    DISTINCT\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_intersect_06() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.sqlite\n\n    from album\n    intersect artist\n    \"#).unwrap_err(),\n        @r\"\n    Error: The dialect SQLiteDialect does not support INTERSECT ALL\n    ↳ Hint: providing more column information will allow the query to be translated to an anti-join.\n    \"\n    );\n}\n\n#[test]\nfn test_intersect_07() {\n    assert_snapshot!(compile(r#\"\n    from ds2 = foo.t1\n    join side:inner ds1 = bar.t2 (ds2.idx==ds1.idx)\n    aggregate { count this }\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      COUNT(*)\n    FROM\n      foo.t1 AS ds2\n      INNER JOIN bar.t2 AS ds1 ON ds2.idx = ds1.idx\n    \"\n    );\n}\n\n#[test]\nfn test_sort_in_nested_join() {\n    assert_snapshot!(compile(r#\"\n    from albums\n    join side:left (\n        from artists\n        sort {-`artist-id`}\n        take 10\n    ) (this.artist_id == that.artist_id) | take 10\n    \"#).unwrap(),\n        @r#\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        artists\n      ORDER BY\n        \"artist-id\" DESC\n      LIMIT\n        10\n    )\n    SELECT\n      albums.*,\n      table_0.*\n    FROM\n      albums\n      LEFT OUTER JOIN table_0 ON albums.artist_id = table_0.artist_id\n    LIMIT\n      10\n    \"#\n    );\n}\n\n#[test]\nfn test_sort_in_nested_join_with_extra_derive_and_select() {\n    // #5302\n    assert_snapshot!(compile(r#\"\n    from albums\n    join side:left  (\n      from artists\n      derive {\n        my_new_col = f\"artist: {name}\"\n      }\n      group {my_new_col} (aggregate { first_name = first this.`name`})\n      sort {this.my_new_col, first_name}\n      derive {new_name = first_name, other_new_name = first_name}\n      select {this.my_new_col, this.new_name, this.other_new_name}\n    ) (this.id == that.my_new_col)\n    \"#).unwrap(),\n        @r\"\n    WITH table_1 AS (\n      SELECT\n        CONCAT('artist: ', name) AS my_new_col,\n        FIRST_VALUE(name) AS _expr_0\n      FROM\n        artists\n      GROUP BY\n        CONCAT('artist: ', name)\n    ),\n    table_2 AS (\n      SELECT\n        my_new_col,\n        _expr_0 AS new_name,\n        _expr_0 AS other_new_name,\n        _expr_0\n      FROM\n        table_1\n    ),\n    table_0 AS (\n      SELECT\n        my_new_col,\n        new_name,\n        other_new_name,\n        _expr_0\n      FROM\n        table_2\n    )\n    SELECT\n      albums.*,\n      table_0.my_new_col,\n      table_0.new_name,\n      table_0.other_new_name\n    FROM\n      albums\n      LEFT OUTER JOIN table_0 ON albums.id = table_0.my_new_col\n    \"\n    );\n}\n\n#[test]\nfn test_sort_in_nested_append() {\n    assert_snapshot!(compile(r#\"\n    from `albums`\n    select { `album_id`, `title` }\n    sort {+`album_id`}\n    take 2\n    append (\n        from `albums`\n        select { `album_id`, `title` }\n        sort {-`album_id`}\n        take 2\n    )\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      *\n    FROM\n      (\n        SELECT\n          album_id,\n          title\n        FROM\n          albums\n        ORDER BY\n          album_id\n        LIMIT\n          2\n      ) AS table_2\n    UNION\n    ALL\n    SELECT\n      *\n    FROM\n      (\n        SELECT\n          album_id,\n          title\n        FROM\n          albums\n        ORDER BY\n          album_id DESC\n        LIMIT\n          2\n      ) AS table_3\n    \"\n    );\n}\n\n#[test]\nfn test_sort_select_redundant_cte() {\n    assert_snapshot!((compile(r#\"\n    let a = (\n      from sometable\n      sort {foo}\n      select {\n        foo\n      }\n    )\n    let b = (\n      from a\n    )\n    from b\n    \"#\n    ).unwrap()), @r\"\n    WITH a AS (\n      SELECT\n        foo\n      FROM\n        sometable\n    ),\n    b AS (\n      SELECT\n        foo\n      FROM\n        a\n    )\n    SELECT\n      foo\n    FROM\n      b\n    ORDER BY\n      foo\n    \");\n}\n\n#[test]\nfn test_column_name_extraction_in_s_strings() {\n    assert_snapshot!(compile(r#\"\nfrom s\"SELECT album_id, artist_id `title` FROM `albums`\"\njoin side:left (\n    s\"SELECT id, name FROM `artists`\"\n) (this.artist_id == that.id)\n\"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        album_id,\n        artist_id `title`\n      FROM\n        `albums`\n    ),\n    table_1 AS (\n      SELECT\n        id,\n        name\n      FROM\n        `artists`\n    )\n    SELECT\n      table_0.artist_id,\n      table_0.album_id,\n      table_0.title,\n      table_1.id,\n      table_1.name\n    FROM\n      table_0\n      LEFT OUTER JOIN table_1 ON table_0.artist_id = table_1.id\n        \"\n    )\n}\n\n#[test]\nfn test_rn_ids_are_unique() {\n    // this is wrong, output will have duplicate y_id and x_id\n    assert_snapshot!((compile(r###\"\n    from y_orig\n    group {y_id} (\n        take 2 # take 1 uses `distinct` instead of partitioning, which might be a separate bug\n    )\n    group {x_id} (\n        take 3\n    )\n    \"###).unwrap()), @r\"\n    WITH table_1 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (PARTITION BY y_id) AS _expr_1\n      FROM\n        y_orig\n    ),\n    table_0 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (PARTITION BY x_id) AS _expr_0\n      FROM\n        table_1\n      WHERE\n        _expr_1 <= 2\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      _expr_0 <= 3\n    \");\n}\n\n#[test]\nfn test_quoting_01() {\n    // GH-#822\n    assert_snapshot!((compile(r###\"\n    prql target:sql.postgres\n    let UPPER = (\n        default_db.lower\n    )\n    from UPPER\n    join `some_schema.tablename` (==id)\n    derive `from` = 5\n    \"###).unwrap()), @r#\"\n    WITH \"UPPER\" AS (\n      SELECT\n        *\n      FROM\n        lower\n    )\n    SELECT\n      \"UPPER\".*,\n      \"some_schema.tablename\".*,\n      5 AS \"from\"\n    FROM\n      \"UPPER\"\n      INNER JOIN \"some_schema.tablename\" ON \"UPPER\".id = \"some_schema.tablename\".id\n    \"#);\n}\n\n#[test]\nfn test_quoting_02() {\n    // GH-1493\n    let query = r###\"\n    from `dir/*.parquet`\n    \"###;\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      *\n    FROM\n      \"dir/*.parquet\"\n    \"#);\n}\n\n#[test]\nfn test_quoting_03() {\n    // GH-#852\n    assert_snapshot!((compile(r###\"\n    prql target:sql.bigquery\n    from `schema.table`\n    join `schema.table2` (==id)\n    join c = `schema.t-able` (`schema.table`.id == c.id)\n    \"###).unwrap()), @r\"\n    SELECT\n      `schema.table`.*,\n      `schema.table2`.*,\n      c.*\n    FROM\n      `schema.table`\n      INNER JOIN `schema.table2` ON `schema.table`.id = `schema.table2`.id\n      INNER JOIN `schema.t-able` AS c ON `schema.table`.id = c.id\n    \");\n}\n\n#[test]\nfn test_quoting_04() {\n    assert_snapshot!((compile(r###\"\n    from table\n    select `first name`\n    \"###).unwrap()), @r#\"\n    SELECT\n      \"first name\"\n    FROM\n      \"table\"\n    \"#);\n}\n\n#[test]\nfn test_quoting_05() {\n    assert_snapshot!((compile(r###\"\n        from as = Assessment\n    \"###).unwrap()), @r#\"\n    SELECT\n      *\n    FROM\n      \"Assessment\" AS \"as\"\n    \"#);\n}\n\n#[test]\nfn test_quoting_06() {\n    let prql = \"\nprql target:sql.bigquery\n\nfrom `some_dataset.demo`\nselect {`hash`}\n\";\n\n    assert_snapshot!(compile(prql).unwrap(), @r\"\n    SELECT\n      `hash`\n    FROM\n      `some_dataset.demo`\n    \");\n}\n\n#[test]\nfn test_sorts_01() {\n    assert_snapshot!((compile(r###\"\n    from invoices\n    sort {issued_at, -amount, +num_of_articles}\n    \"###\n    ).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      invoices\n    ORDER BY\n      issued_at,\n      amount DESC,\n      num_of_articles\n    \");\n\n    assert_snapshot!((compile(r#\"\n    from x\n    derive somefield = \"something\"\n    sort {somefield}\n    select {renamed = somefield}\n    \"#\n    ).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        'something' AS renamed,\n        'something' AS _expr_0\n      FROM\n        x\n    )\n    SELECT\n      renamed\n    FROM\n      table_0\n    ORDER BY\n      renamed\n    \");\n}\n\n#[test]\nfn test_sorts_02() {\n    // issue #3129\n\n    assert_snapshot!((compile(r###\"\n    let x = (\n      from table\n      sort index\n      select {fieldA}\n    )\n    from x\n    \"###\n    ).unwrap()), @r#\"\n    WITH table_0 AS (\n      SELECT\n        \"fieldA\",\n        \"index\"\n      FROM\n        \"table\"\n    ),\n    x AS (\n      SELECT\n        \"fieldA\",\n        \"index\"\n      FROM\n        table_0\n    )\n    SELECT\n      \"fieldA\"\n    FROM\n      x\n    ORDER BY\n      \"index\"\n    \"#);\n}\n\n#[test]\nfn test_sorts_03() {\n    // TODO: this is invalid SQL: a._expr_0 does not exist\n    assert_snapshot!((compile(r#\"\n    from a\n    join b side:left (==col)\n    sort a.col\n    select !{a.col}\n    take 5\n    \"#\n    ).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        a.*,\n        b.*,\n        a.col AS _expr_0\n      FROM\n        a\n        LEFT OUTER JOIN b ON a.col = b.col\n      ORDER BY\n        a._expr_0\n      LIMIT\n        5\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    ORDER BY\n      _expr_0\n    \");\n}\n\n#[test]\nfn test_sort_before_aggregate() {\n    assert_snapshot!((compile(r#\"\n    from a\n    sort a.col\n    aggregate { result = sum a.col_to_agg }\n    \"#\n    ).unwrap()), @r\"\n    SELECT\n      COALESCE(SUM(col_to_agg), 0) AS result\n    FROM\n      a\n    \");\n}\n\n#[test]\nfn test_numbers() {\n    let query = r###\"\n    from numbers\n    select {\n        v = 5.000_000_1,\n        w = 5_000,\n        x = 5,\n        y = 5.0,\n        z = 5.00,\n    }\n    \"###;\n\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    SELECT\n      5.0000001 AS v,\n      5000 AS w,\n      5 AS x,\n      5.0 AS y,\n      5.0 AS z\n    FROM\n      numbers\n    \");\n}\n\n#[test]\nfn test_ranges() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    derive {\n      close = (distance | in ..50),\n      middle = (distance | in 50..100),\n      far = (distance | in 100..),\n      (country_founding | in @1776-07-04..@1787-09-17)\n    }\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      distance <= 50 AS close,\n      distance BETWEEN 50 AND 100 AS middle,\n      distance >= 100 AS far,\n      country_founding BETWEEN DATE '1776-07-04' AND DATE '1787-09-17'\n    FROM\n      employees\n    \");\n}\n\n#[test]\nfn test_in_values_01() {\n    assert_snapshot!((compile(r#\"\n    from employees\n    filter (title | in [\"Sales Manager\", \"Sales Support Agent\"])\n    filter (employee_id | in [1, 2, 5])\n    filter (f\"{emp_group}.{role}\" | in [\"sales_ne.mgr\", \"sales_mw.mgr\"])\n    filter (s\"{metadata} ->> '$.location'\" | in [\"Northeast\", \"Midwest\"])\n    \"#).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    WHERE\n      title IN ('Sales Manager', 'Sales Support Agent')\n      AND employee_id IN (1, 2, 5)\n      AND CONCAT(emp_group, '.', role) IN ('sales_ne.mgr', 'sales_mw.mgr')\n      AND metadata ->> '$.location' IN ('Northeast', 'Midwest')\n    \");\n}\n\n#[test]\n#[ignore] // unimplemented, column ref type resolution required\nfn test_in_values_02() {\n    assert_snapshot!((compile(r#\"\n    let allowed_titles = [\"Sales Manager\", \"Sales Support Agent\"]\n\n    from employees\n    derive {allowed_ids = [1, 2, 5]}\n    filter (title | in allowed_titles)\n    filter (title | in allowed_ids)\n    \"#).unwrap()), @r###\"\n    SELECT\n      *\n    FROM\n      employees\n    WHERE\n      title IN ('Sales Manager', 'Sales Support Agent')\n    \"###);\n}\n\n#[test]\n#[ignore] // unimplemented, column ref type resolution required\nfn test_in_values_03() {\n    assert_snapshot!((compile(r#\"\n    from employees\n    derive allowed_titles = case [\n        is_guest => [\"Sales Manager\"],\n        true => [\"Sales Manager\", \"Sales Support Agent\"],\n    ]\n    filter (title | in allowed_titles)\n    \"#).unwrap()), @r###\"\n    SELECT\n      *\n    FROM\n      employees\n    WHERE\n      title IN ('Sales Manager', 'Sales Support Agent')\n    \"###);\n}\n\n#[test]\nfn test_not_in_values() {\n    assert_snapshot!((compile(r#\"\n    from employees\n    filter !(title | in [\"Sales Manager\", \"Sales Support Agent\"])\n    \"#).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    WHERE\n      NOT title IN ('Sales Manager', 'Sales Support Agent')\n    \");\n}\n\n#[test]\nfn test_in_no_values() {\n    assert_snapshot!((compile(r#\"\n    from employees\n    filter (title | in [])\n    \"#).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    WHERE\n      false\n    \");\n}\n\n#[test]\nfn test_in_values_err_01() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    derive { ng = ([1, 2] | in [3, 4]) }\n    \"###).unwrap_err()), @r\"\n    Error:\n       ╭─[ :3:29 ]\n       │\n     3 │     derive { ng = ([1, 2] | in [3, 4]) }\n       │                             ────┬────\n       │                                 ╰────── args to `std.array_in` must be an expression and an array\n    ───╯\n    \");\n}\n\n#[test]\nfn test_interval() {\n    let query = r###\"\n    from projects\n    derive first_check_in = start + 10days\n    \"###;\n\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      *,\n      \"start\" + INTERVAL 10 DAY AS first_check_in\n    FROM\n      projects\n    \"#);\n\n    let query = r###\"\n    prql target:sql.postgres\n\n    from projects\n    derive first_check_in = start + 10days\n    \"###;\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      *,\n      \"start\" + INTERVAL '10 DAY' AS first_check_in\n    FROM\n      projects\n    \"#);\n\n    let query = r###\"\n    prql target:sql.glaredb\n\n    from projects\n    derive first_check_in = start + 10days\n    \"###;\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      *,\n      \"start\" + INTERVAL '10 DAY' AS first_check_in\n    FROM\n      projects\n    \"#);\n\n    let query = r###\"\n    prql target:sql.snowflake\n\n    from projects\n    derive first_check_in = start + 10days\n    \"###;\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      *,\n      \"start\" + INTERVAL '10 DAY' AS \"first_check_in\"\n    FROM\n      \"projects\"\n    \"#);\n}\n\n#[test]\nfn test_dates() {\n    assert_snapshot!((compile(r###\"\n    from to_do_empty_table\n    derive {\n        date = @2011-02-01,\n        timestamp = @2011-02-01T10:00,\n        time = @14:00,\n        # datetime = @2011-02-01T10:00<datetime>,\n    }\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      DATE '2011-02-01' AS date,\n      TIMESTAMP '2011-02-01T10:00' AS timestamp,\n      TIME '14:00' AS time\n    FROM\n      to_do_empty_table\n    \");\n}\n\n#[test]\nfn test_window_functions_00() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    group last_name (\n        derive {count first_name}\n    )\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      COUNT(*) OVER (PARTITION BY last_name)\n    FROM\n      employees\n    \");\n}\n\n#[test]\nfn test_window_functions_02() {\n    let query = r#\"\n    from co=cust_order\n    join ol=order_line (==order_id)\n    derive {\n        order_month = s\"TO_CHAR({co.order_date}, '%Y-%m')\",\n        order_day = s\"TO_CHAR({co.order_date}, '%Y-%m-%d')\",\n    }\n    group {order_month, order_day} (\n        aggregate {\n            num_orders = s\"COUNT(DISTINCT {co.order_id})\",\n            num_books = count ol.book_id,\n            total_price = sum ol.price,\n        }\n    )\n    group {order_month} (\n        sort order_day\n        window expanding:true (\n            derive {running_total_num_books = sum num_books}\n        )\n    )\n    sort order_day\n    derive {num_books_last_week = lag 7 num_books}\n    \"#;\n\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        TO_CHAR(co.order_date, '%Y-%m') AS order_month,\n        TO_CHAR(co.order_date, '%Y-%m-%d') AS order_day,\n        COUNT(DISTINCT co.order_id) AS num_orders,\n        COUNT(*) AS num_books,\n        COALESCE(SUM(ol.price), 0) AS total_price\n      FROM\n        cust_order AS co\n        INNER JOIN order_line AS ol ON co.order_id = ol.order_id\n      GROUP BY\n        TO_CHAR(co.order_date, '%Y-%m'),\n        TO_CHAR(co.order_date, '%Y-%m-%d')\n    )\n    SELECT\n      order_month,\n      order_day,\n      num_orders,\n      num_books,\n      total_price,\n      SUM(num_books) OVER (\n        PARTITION BY order_month\n        ORDER BY\n          order_day ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n      ) AS running_total_num_books,\n      LAG(num_books, 7) OVER (\n        ORDER BY\n          order_day\n      ) AS num_books_last_week\n    FROM\n      table_0\n    ORDER BY\n      order_day\n    \");\n}\n\n#[test]\nfn test_window_functions_03() {\n    // lag must be recognized as window function, even outside of group context\n    // rank must not have two OVER clauses\n    let query = r###\"\n    from daily_orders\n    derive {last_week = lag 7 num_orders}\n    derive {first_count = first num_orders}\n    derive {last_count = last num_orders}\n    group month (\n      derive {total_month = sum num_orders}\n    )\n    \"###;\n\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    SELECT\n      *,\n      LAG(num_orders, 7) OVER () AS last_week,\n      FIRST_VALUE(num_orders) OVER () AS first_count,\n      LAST_VALUE(num_orders) OVER () AS last_count,\n      SUM(num_orders) OVER (PARTITION BY month) AS total_month\n    FROM\n      daily_orders\n    \");\n}\n\n#[test]\nfn test_window_functions_04() {\n    // sort does not affects into groups, group undoes sorting\n    let query = r###\"\n    from daily_orders\n    sort day\n    group month (derive {total_month = rank day})\n    derive {last_week = lag 7 num_orders}\n    \"###;\n\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    SELECT\n      *,\n      RANK() OVER (PARTITION BY month) AS total_month,\n      LAG(num_orders, 7) OVER () AS last_week\n    FROM\n      daily_orders\n    \");\n}\n\n#[test]\nfn test_window_functions_05() {\n    // sort does not leak out of groups\n    let query = r###\"\n    from daily_orders\n    sort day\n    group month (sort num_orders | window expanding:true (derive {rank day}))\n    derive {num_orders_last_week = lag 7 num_orders}\n    \"###;\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    SELECT\n      *,\n      RANK() OVER (\n        PARTITION BY month\n        ORDER BY\n          num_orders\n      ),\n      LAG(num_orders, 7) OVER () AS num_orders_last_week\n    FROM\n      daily_orders\n    \");\n}\n\n#[test]\nfn test_window_functions_06() {\n    // detect sum as a window function, even without group or window\n    assert_snapshot!((compile(r###\"\n    from foo\n    derive {a = sum b}\n    group c (\n        derive {d = sum b}\n    )\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      SUM(b) OVER () AS a,\n      SUM(b) OVER (PARTITION BY c) AS d\n    FROM\n      foo\n    \");\n}\n\n#[test]\nfn test_window_functions_07() {\n    assert_snapshot!((compile(r###\"\n    from foo\n    window expanding:true (\n        derive {running_total = sum b}\n    )\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      SUM(b) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total\n    FROM\n      foo\n    \");\n}\n\n#[test]\nfn test_window_functions_08() {\n    assert_snapshot!((compile(r###\"\n    from foo\n    window rolling:3 (\n        derive {last_three = sum b}\n    )\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      SUM(b) OVER (ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS last_three\n    FROM\n      foo\n    \");\n}\n\n#[test]\nfn test_window_functions_09() {\n    assert_snapshot!((compile(r###\"\n    from foo\n    window rows:0..4 (\n        derive {next_four_rows = sum b}\n    )\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      SUM(b) OVER (\n        ROWS BETWEEN CURRENT ROW\n        AND 4 FOLLOWING\n      ) AS next_four_rows\n    FROM\n      foo\n    \");\n}\n\n#[test]\nfn test_window_functions_10() {\n    assert_snapshot!((compile(r###\"\n    from foo\n    sort day\n    window range:-4..4 (\n        derive {next_four_days = sum b}\n    )\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      SUM(b) OVER (\n        ORDER BY\n          day RANGE BETWEEN 4 PRECEDING AND 4 FOLLOWING\n      ) AS next_four_days\n    FROM\n      foo\n    ORDER BY\n      day\n    \");\n}\n\n#[test]\nfn test_window_functions_11() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    sort age\n    derive {num = row_number this}\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      ROW_NUMBER() OVER (\n        ORDER BY\n          age\n      ) AS num\n    FROM\n      employees\n    ORDER BY\n      age\n    \");\n}\n\n#[test]\nfn test_window_functions_12() {\n    // window params need to be simple expressions\n\n    assert_snapshot!((compile(r###\"\n    from x\n    derive {b = lag 1 a}\n    window (\n      sort b\n      derive {c = lag 1 a}\n    )\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        LAG(a, 1) OVER () AS b\n      FROM\n        x\n    )\n    SELECT\n      *,\n      LAG(a, 1) OVER (\n        ORDER BY\n          b\n      ) AS c\n    FROM\n      table_0\n    ORDER BY\n      b\n    \");\n\n    assert_snapshot!((compile(r###\"\n    from x\n    derive {b = lag 1 a}\n    group b (\n      derive {c = lag 1 a}\n    )\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        LAG(a, 1) OVER () AS b,\n        *\n      FROM\n        x\n    )\n    SELECT\n      *,\n      LAG(a, 1) OVER (PARTITION BY b) AS c\n    FROM\n      table_0\n    \");\n}\n\n#[test]\nfn test_window_functions_13() {\n    // window params need to be simple expressions\n\n    assert_snapshot!((compile(r###\"\n    from tracks\n    group {album_id} (\n      window (derive {grp = milliseconds - (row_number this)})\n    )\n    group {grp} (\n      window (derive {count = row_number this})\n    )\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (PARTITION BY album_id) AS _expr_0\n      FROM\n        tracks\n    )\n    SELECT\n      milliseconds - _expr_0 AS grp,\n      *,\n      ROW_NUMBER() OVER (PARTITION BY milliseconds - _expr_0) AS count\n    FROM\n      table_0\n    \");\n}\n\n#[test]\nfn test_window_single_item_range() {\n    assert_snapshot!(compile(r###\"\n      from login_event\n      window rows:1..1 (\n        sort time_upload\n        derive {\n            last_user = min user_id\n        }\n      )\n    \"###).unwrap(), @r\"\n    SELECT\n      *,\n      MIN(user_id) OVER (\n        ORDER BY\n          time_upload ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING\n      ) AS last_user\n    FROM\n      login_event\n    ORDER BY\n      time_upload\n    \");\n}\n\n#[test]\nfn test_name_resolving() {\n    let query = r###\"\n    from numbers\n    derive x = 5\n    select {y = 6, z = x + y + a}\n    \"###;\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    SELECT\n      6 AS y,\n      5 + 6 + a AS z\n    FROM\n      numbers\n    \");\n}\n\n#[test]\nfn test_strings() {\n    let query = r#\"\n    from empty_table_to_do\n    select {\n        x = \"two households'\",\n        y = 'two households\"',\n        z = f\"a {x} b' {y} c\",\n        v = f'a {x} b\" {y} c',\n    }\n    \"#;\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      'two households''' AS x,\n      'two households\"' AS y,\n      CONCAT(\n        'a ',\n        'two households''',\n        ' b'' ',\n        'two households\"',\n        ' c'\n      ) AS z,\n      CONCAT(\n        'a ',\n        'two households''',\n        ' b\" ',\n        'two households\"',\n        ' c'\n      ) AS v\n    FROM\n      empty_table_to_do\n    \"#);\n}\n\n#[test]\nfn test_filter() {\n    // https://github.com/PRQL/prql/issues/469\n    let query = r###\"\n    from employees\n    filter {age > 25, age < 40}\n    \"###;\n\n    assert!(compile(query).is_err());\n\n    assert_snapshot!((compile(r###\"\n    from employees\n    filter age > 25 && age < 40\n    \"###).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    WHERE\n      age > 25\n      AND age < 40\n    \");\n\n    assert_snapshot!((compile(r###\"\n    from employees\n    filter age > 25\n    filter age < 40\n    \"###).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    WHERE\n      age > 25\n      AND age < 40\n    \");\n}\n\n#[test]\nfn test_nulls_01() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    select amount = null\n    \"###).unwrap()), @r\"\n    SELECT\n      NULL AS amount\n    FROM\n      employees\n    \");\n}\n\n#[test]\nfn test_nulls_02() {\n    // coalesce\n    assert_snapshot!((compile(r###\"\n    from employees\n    derive amount = amount + 2 ?? 3 * 5\n    \"###).unwrap()), @r\"\n    SELECT\n      *,\n      COALESCE(amount + 2, 3 * 5) AS amount\n    FROM\n      employees\n    \");\n}\n\n#[test]\nfn test_nulls_03() {\n    // IS NULL\n    assert_snapshot!((compile(r###\"\n    from employees\n    filter first_name == null && null == last_name\n    \"###).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    WHERE\n      first_name IS NULL\n      AND last_name IS NULL\n    \");\n}\n\n#[test]\nfn test_nulls_04() {\n    // IS NOT NULL\n    assert_snapshot!((compile(r###\"\n    from employees\n    filter first_name != null && null != last_name\n    \"###).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    WHERE\n      first_name IS NOT NULL\n      AND last_name IS NOT NULL\n    \");\n}\n\n#[test]\nfn test_take_01() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    take ..10\n    \"###).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    LIMIT\n      10\n    \");\n}\n\n#[test]\nfn test_take_02() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    take 5..10\n    \"###).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    LIMIT\n      6 OFFSET 4\n    \");\n}\n\n#[test]\nfn test_take_03() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    take 5..\n    \"###).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees OFFSET 4\n    \");\n}\n\n#[test]\nfn test_take_04() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    take 5..5\n    \"###).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    LIMIT\n      1 OFFSET 4\n    \");\n}\n\n#[test]\nfn test_take_05() {\n    // should be one SELECT\n    assert_snapshot!((compile(r###\"\n    from employees\n    take 11..20\n    take 1..5\n    \"###).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      employees\n    LIMIT\n      5 OFFSET 10\n    \");\n}\n\n#[test]\nfn test_take_06() {\n    // should be two SELECTTs\n    assert_snapshot!((compile(r###\"\n    from employees\n    take 11..20\n    sort name\n    take 1..5\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        employees\n      LIMIT\n        10 OFFSET 10\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    ORDER BY\n      name\n    LIMIT\n      5\n    \");\n}\n\n#[test]\nfn test_take_07() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    take 0..1\n    \"###).unwrap_err()), @r\"\n    Error:\n       ╭─[ :3:5 ]\n       │\n     3 │     take 0..1\n       │     ────┬────\n       │         ╰────── take expected a positive int range, but found 0..1\n    ───╯\n    \");\n}\n\n#[test]\nfn test_take_08() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    take (-1..)\n    \"###).unwrap_err()), @r\"\n    Error:\n       ╭─[ :3:5 ]\n       │\n     3 │     take (-1..)\n       │     ─────┬─────\n       │          ╰─────── take expected a positive int range, but found -1..\n    ───╯\n    \");\n}\n\n#[test]\nfn test_take_09() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    select a\n    take 5..5.6\n    \"###).unwrap_err()), @r\"\n    Error:\n       ╭─[ :4:5 ]\n       │\n     4 │     take 5..5.6\n       │     ─────┬─────\n       │          ╰─────── take expected a positive int range, but found 5..?\n    ───╯\n    \");\n}\n\n#[test]\nfn test_take_10() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    take (-1)\n    \"###).unwrap_err()), @r\"\n    Error:\n       ╭─[ :3:5 ]\n       │\n     3 │     take (-1)\n       │     ────┬────\n       │         ╰────── take expected a positive int range, but found ..-1\n    ───╯\n    \");\n}\n\n#[test]\nfn test_take_mssql() {\n    assert_snapshot!((compile(r#\"\n    prql target:sql.mssql\n\n    from tracks\n    take 3..5\n    \"#).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      tracks\n    ORDER BY\n      (\n        SELECT\n          NULL\n      ) OFFSET 2 ROWS\n    FETCH FIRST\n      3 ROWS ONLY\n    \");\n\n    assert_snapshot!((compile(r#\"\n    prql target:sql.mssql\n\n    from tracks\n    take ..5\n    \"#).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      tracks\n    ORDER BY\n      (\n        SELECT\n          NULL\n      ) OFFSET 0 ROWS\n    FETCH FIRST\n      5 ROWS ONLY\n    \");\n\n    assert_snapshot!((compile(r#\"\n    prql target:sql.mssql\n\n    from tracks\n    take 3..\n    \"#).unwrap()), @r\"\n    SELECT\n      *\n    FROM\n      tracks OFFSET 2 ROWS\n    \");\n}\n\n#[test]\nfn test_mssql_distinct_fetch() {\n    // Issue #5628: MSSQL requires ORDER BY items to appear in SELECT list when DISTINCT is used.\n    // Using (SELECT NULL) for ORDER BY with DISTINCT is invalid in MSSQL.\n\n    // Case 1: UnnamedExpr - simple column reference\n    assert_snapshot!((compile(r#\"\n    prql target:sql.mssql\n\n    from t\n    take 100\n    group {this.`District`} (take 1)\n    select {this.`District`}\n    \"#).unwrap()), @r###\"\n    SELECT\n      DISTINCT \"District\"\n    FROM\n      t\n    ORDER BY\n      \"District\" OFFSET 0 ROWS\n    FETCH FIRST\n      100 ROWS ONLY\n    \"###);\n\n    // Case 2: ExprWithAlias - uses the alias for ORDER BY\n    assert_snapshot!((compile(r#\"\n    prql target:sql.mssql\n\n    from t\n    take 100\n    group {d = this.`District`} (take 1)\n    select {d}\n    \"#).unwrap()), @r###\"\n    SELECT\n      DISTINCT \"District\" AS d\n    FROM\n      t\n    ORDER BY\n      d OFFSET 0 ROWS\n    FETCH FIRST\n      100 ROWS ONLY\n    \"###);\n\n    // Case 3: Multiple columns - uses first column for ORDER BY\n    assert_snapshot!((compile(r#\"\n    prql target:sql.mssql\n\n    from t\n    take 100\n    group {this.`A`, this.`B`} (take 1)\n    select {this.`A`, this.`B`}\n    \"#).unwrap()), @r###\"\n    SELECT\n      DISTINCT \"A\",\n      \"B\"\n    FROM\n      t\n    ORDER BY\n      \"A\" OFFSET 0 ROWS\n    FETCH FIRST\n      100 ROWS ONLY\n    \"###);\n}\n\n#[test]\nfn test_distinct_01() {\n    // window functions cannot materialize into where statement: CTE is needed\n    assert_snapshot!((compile(r###\"\n    from employees\n    derive {rn = row_number id}\n    filter rn > 2\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER () AS rn\n      FROM\n        employees\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      rn > 2\n    \");\n}\n\n#[test]\nfn test_distinct_02() {\n    // basic distinct\n    assert_snapshot!((compile(r###\"\n    from employees\n    select first_name\n    group first_name (take 1)\n    \"###).unwrap()), @r\"\n    SELECT\n      DISTINCT first_name\n    FROM\n      employees\n    \");\n}\n\n#[test]\nfn test_distinct_03() {\n    // distinct on two columns\n    assert_snapshot!((compile(r###\"\n    from employees\n    select {first_name, last_name}\n    group {first_name, last_name} (take 1)\n    \"###).unwrap()), @r\"\n    SELECT\n      DISTINCT first_name,\n      last_name\n    FROM\n      employees\n    \");\n}\n#[test]\nfn test_distinct_04() {\n    // We want distinct only over first_name and last_name, so we can't use a\n    // `DISTINCT *` here.\n    assert_snapshot!((compile(r###\"\n    from employees\n    group {first_name, last_name} (take 1)\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (PARTITION BY first_name, last_name) AS _expr_0\n      FROM\n        employees\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      _expr_0 <= 1\n    \");\n}\n#[test]\nfn test_distinct_05() {\n    // Check that a different order doesn't stop distinct from being used.\n    assert!(compile(\n        \"from employees | select {first_name, last_name} | group {last_name, first_name} (take 1)\"\n    )\n    .unwrap()\n    .contains(\"DISTINCT\"));\n}\n#[test]\nfn test_distinct_06() {\n    // head\n    assert_snapshot!((compile(r###\"\n    from employees\n    group department (take 3)\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (PARTITION BY department) AS _expr_0\n      FROM\n        employees\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      _expr_0 <= 3\n    \");\n}\n#[test]\nfn test_distinct_07() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    group department (sort salary | take 2..3)\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (\n          PARTITION BY department\n          ORDER BY\n            salary\n        ) AS _expr_0\n      FROM\n        employees\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      _expr_0 BETWEEN 2 AND 3\n    \");\n}\n#[test]\nfn test_distinct_08() {\n    assert_snapshot!((compile(r###\"\n    from employees\n    group department (sort salary | take 4..4)\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (\n          PARTITION BY department\n          ORDER BY\n            salary\n        ) AS _expr_0\n      FROM\n        employees\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      _expr_0 = 4\n    \");\n}\n\n#[test]\nfn test_distinct_09() {\n    assert_snapshot!(compile(\"\n    from invoices\n    select {billing_country, billing_city}\n    group {billing_city} (\n      take 1\n    )\n    sort billing_city\n    \").unwrap(), @r\"\n    WITH table_0 AS (\n      SELECT\n        billing_city,\n        billing_country,\n        ROW_NUMBER() OVER (PARTITION BY billing_city) AS _expr_0\n      FROM\n        invoices\n    )\n    SELECT\n      billing_city,\n      billing_country\n    FROM\n      table_0\n    WHERE\n      _expr_0 <= 1\n    ORDER BY\n      billing_city\n    \");\n}\n\n#[test]\nfn test_distinct_on_01() {\n    assert_snapshot!((compile(r###\"\n    prql target:sql.postgres\n\n    from employees\n    group department (\n      sort age\n      take 1\n    )\n    \"###).unwrap()), @r\"\n    SELECT\n      DISTINCT ON (department) *\n    FROM\n      employees\n    ORDER BY\n      department,\n      age\n    \");\n}\n\n#[test]\nfn test_distinct_on_02() {\n    assert_snapshot!((compile(r###\"\n    prql target:sql.duckdb\n\n    from x\n    select {class, begins}\n    group {begins} (take 1)\n    \"###).unwrap()), @r\"\n    SELECT\n      DISTINCT ON (begins) begins,\n      class\n    FROM\n      x\n    \");\n}\n\n#[test]\nfn test_distinct_on_03() {\n    assert_snapshot!((compile(r###\"\n    prql target:sql.duckdb\n\n    from tab1\n    group col1 (\n      take 1\n    )\n    derive foo = 1\n    select foo\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        DISTINCT ON (col1) *\n      FROM\n        tab1\n    )\n    SELECT\n      1 AS foo\n    FROM\n      table_0\n    \");\n}\n\n#[test]\nfn test_distinct_on_04() {\n    assert_snapshot!((compile(r###\"\n    prql target:sql.duckdb\n\n    from a\n    join b (b.a_id == a.id)\n    group {a.id} (\n      sort b.x\n      take 1\n    )\n    select {a.id, b.y}\n    \"###).unwrap()), @r\"\n    SELECT\n      DISTINCT ON (a.id) a.id,\n      b.y\n    FROM\n      a\n      INNER JOIN b ON b.a_id = a.id\n    ORDER BY\n      a.id,\n      b.x\n    \");\n}\n\n#[test]\nfn test_group_take_n_01() {\n    assert_snapshot!((compile(r###\"\n    prql target:sql.postgres\n\n    from employees\n    group department (\n      sort age\n      take 2\n    )\n    \"###).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (\n          PARTITION BY department\n          ORDER BY\n            age\n        ) AS _expr_0\n      FROM\n        employees\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      _expr_0 <= 2\n    \");\n}\n\n#[test]\nfn test_group_take_n_02() {\n    assert_snapshot!((compile(r###\"\n    prql target:sql.postgres\n\n    from employees\n    group department (\n      sort age\n      take 2..\n    )\n    \"###).unwrap()),  @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (\n          PARTITION BY department\n          ORDER BY\n            age\n        ) AS _expr_0\n      FROM\n        employees\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      _expr_0 >= 2\n    \");\n}\n\n#[test]\nfn test_join() {\n    assert_snapshot!((compile(r###\"\n    from x\n    join y (==id)\n    \"###).unwrap()), @r\"\n    SELECT\n      x.*,\n      y.*\n    FROM\n      x\n      INNER JOIN y ON x.id = y.id\n    \");\n\n    compile(\"from x | join y {==x.id}\").unwrap_err();\n}\n\n#[test]\nfn test_join_side_literal() {\n    assert_snapshot!((compile(r###\"\n    let my_side = \"right\"\n\n    from x\n    join y (==id) side:my_side\n    \"###).unwrap()), @r\"\n    SELECT\n      x.*,\n      y.*\n    FROM\n      x\n      RIGHT OUTER JOIN y ON x.id = y.id\n    \");\n}\n\n#[test]\nfn test_join_side_literal_err() {\n    assert_snapshot!((compile(r###\"\n    let my_side = 42\n\n    from x\n    join y (==id) side:my_side\n    \"###).unwrap_err()), @r\"\n    Error:\n       ╭─[ :5:24 ]\n       │\n     5 │     join y (==id) side:my_side\n       │                        ───┬───\n       │                           ╰───── `side` expected inner, left, right or full, but found 42\n    ───╯\n    \");\n}\n\n#[test]\nfn test_join_side_literal_via_func() {\n    assert_snapshot!((compile(r###\"\n    let my_join = func m <relation> c s <text>:\"right\" tbl <relation> -> (\n        join side:_param.s m (c == that.k) tbl\n    )\n\n    from x\n    my_join default_db.y this.id s:\"left\"\n    \"###).unwrap()), @r\"\n    SELECT\n      x.*,\n      y.*\n    FROM\n      x\n      LEFT OUTER JOIN y ON x.id = y.k\n    \");\n}\n\n#[test]\nfn test_join_side_literal_via_func_err() {\n    assert_snapshot!((compile(r###\"\n    let my_join = func m <relation> c s <text>:\"right\" tbl <relation> -> (\n        join side:_param.s m (c == that.k) tbl\n    )\n\n    from x\n    my_join default_db.y this.id s:\"four\"\n    \"###).unwrap_err()), @r#\"\n    Error:\n       ╭─[ :3:19 ]\n       │\n     3 │         join side:_param.s m (c == that.k) tbl\n       │                   ────┬───\n       │                       ╰───── `side` expected inner, left, right or full, but found \"four\"\n    ───╯\n    \"#);\n}\n\n#[test]\nfn test_join_with_param_name_collision() {\n    // Regression test for issue #5015\n    // When joining tables that both have a column named \"source\",\n    // the compiler should not report an ambiguous name error due to\n    // the \"source\" parameter from the std library's \"from\" function.\n    assert_snapshot!((compile(r###\"\n    let a = (\n      from events_a\n      select {\n        event_id,\n        source,\n      }\n    )\n\n    from a\n    join a (==event_id)\n    select {\n      event_id = a.event_id,\n    }\n    \"###).unwrap()), @r\"\n    WITH a AS (\n      SELECT\n        event_id,\n        source\n      FROM\n        events_a\n    )\n    SELECT\n      table_0.event_id\n    FROM\n      a\n      INNER JOIN a AS table_0 ON a.event_id = table_0.event_id\n    \");\n}\n\n#[test]\nfn test_from_json() {\n    // Test that the SQL generated from the JSON of the PRQL is the same as the raw PRQL\n    let original_prql = r#\"\n    from e=employees\n    join salaries (==emp_no)\n    group {e.emp_no, e.gender} (\n      aggregate {\n        emp_salary = average salaries.salary\n      }\n    )\n    join de=dept_emp (==emp_no)\n    join dm=dept_manager (\n      (dm.dept_no == de.dept_no) && s\"(de.from_date, de.to_date) OVERLAPS (dm.from_date, dm.to_date)\"\n    )\n    group {dm.emp_no, gender} (\n      aggregate {\n        salary_avg = average emp_salary,\n        salary_sd = stddev emp_salary\n      }\n    )\n    derive mng_no = emp_no\n    join managers=employees (==emp_no)\n    derive mng_name = s\"managers.first_name || ' ' || managers.last_name\"\n    select {mng_name, managers.gender, salary_avg, salary_sd}\n    \"#;\n\n    let source_tree = SourceTree::from(original_prql);\n\n    let sql_from_prql = Ok(prqlc::prql_to_pl_tree(&source_tree).unwrap())\n        .and_then(|ast| prqlc::semantic::resolve_and_lower(ast, &[], None))\n        .and_then(|rq| sql::compile(rq, &Options::default()))\n        .unwrap();\n\n    let sql_from_json = prqlc::prql_to_pl(original_prql)\n        .map(|x| prqlc::json::from_pl(&x).unwrap())\n        .map(|json| prqlc::json::to_pl(&json).unwrap())\n        .and_then(prqlc::pl_to_rq)\n        .and_then(|rq| prqlc::rq_to_sql(rq, &Options::default()))\n        .unwrap();\n\n    assert_eq!(sql_from_prql, sql_from_json);\n}\n\n#[test]\nfn test_f_string() {\n    let query = r#\"\n    from employees\n    derive age = year_born - s'now()'\n    select {\n        f\"Hello my name is {prefix}{first_name} {last_name}\",\n        f\"and I am {age} years old.\"\n    }\n    \"#;\n\n    assert_snapshot!(\n      compile(query).unwrap(),\n        @r\"\n    SELECT\n      CONCAT(\n        'Hello my name is ',\n        prefix,\n        first_name,\n        ' ',\n        last_name\n      ),\n      CONCAT('and I am ', year_born - now(), ' years old.')\n    FROM\n      employees\n    \"\n    );\n\n    assert_snapshot!(\n        prqlc::compile(\n          query,\n          &Options::default()\n              .no_signature()\n              .with_target(Target::Sql(Some(sql::Dialect::SQLite)))\n\n      ).unwrap(),\n          @r\"\n    SELECT\n      'Hello my name is ' || prefix || first_name || ' ' || last_name,\n      'and I am ' || year_born - now() || ' years old.'\n    FROM\n      employees\n    \"\n    );\n}\n\n#[test]\nfn test_sql_of_ast_1() {\n    let query = r#\"\n    from employees\n    filter country == \"USA\"\n    group {title, country} (\n        aggregate {average salary}\n    )\n    sort title\n    take 20\n    \"#;\n\n    let sql = compile(query).unwrap();\n    assert_snapshot!(sql,\n        @r\"\n    SELECT\n      title,\n      country,\n      AVG(salary)\n    FROM\n      employees\n    WHERE\n      country = 'USA'\n    GROUP BY\n      title,\n      country\n    ORDER BY\n      title\n    LIMIT\n      20\n    \"\n    );\n}\n\n#[test]\nfn test_sql_of_ast_02() {\n    assert_snapshot!(compile(r#\"\n    from employees\n    aggregate sum_salary = s\"sum({salary})\"\n    filter sum_salary > 100\n    \"#).unwrap(), @r\"\n    SELECT\n      sum(salary) AS sum_salary\n    FROM\n      employees\n    HAVING\n      sum(salary) > 100\n    \");\n}\n\n#[test]\nfn test_bare_s_string() {\n    let query = r#\"\n    let grouping = s\"\"\"\n        SELECT SUM(a)\n        FROM tbl\n        GROUP BY\n          GROUPING SETS\n          ((b, c, d), (d), (b, d))\n      \"\"\"\n    from grouping\n    \"#;\n\n    let sql = compile(query).unwrap();\n    assert_snapshot!(sql,\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        SUM(a)\n      FROM\n        tbl\n      GROUP BY\n        GROUPING SETS ((b, c, d), (d), (b, d))\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_bare_s_string_01() {\n    // Test that case insensitive SELECT is accepted. We allow it as it is valid SQL.\n    assert_snapshot!(compile(r#\"\n    let a = s\"select insensitive from rude\"\n    from a\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        insensitive\n      from\n        rude\n    )\n    SELECT\n      insensitive\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_bare_s_string_02() {\n    // Check a mixture of cases for good measure.\n    assert_snapshot!(compile(r#\"\n    let a = s\"sElEcT insensitive from rude\"\n    from a\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        insensitive\n      from\n        rude\n    )\n    SELECT\n      insensitive\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_bare_s_string_03() {\n    // Check SELECT\\n.\n    assert_snapshot!(compile(r#\"\n    let a = s\"\n    SELECT\n      foo\n    FROM\n      bar\"\n\n    from a\n    \"#).unwrap(), @r\"\n    WITH table_0 AS (\n      SELECT\n        foo\n      FROM\n        bar\n    )\n    SELECT\n      foo\n    FROM\n      table_0\n    \");\n}\n\n#[test]\nfn test_bare_s_string_04() {\n    assert_snapshot!(compile(r#\"\n    s\"SELECTTfoo\"\n    \"#).unwrap_err(), @r\"\n    Error: s-strings representing a table must start with `SELECT `\n    ↳ Hint: this is a limitation by current compiler implementation\n    \");\n}\n\n#[test]\n// Confirm that a regular expr_call in a table definition works as expected.\nfn test_table_definition_with_expr_call() {\n    let query = r###\"\n    let e = take 4 (from employees)\n    from e\n    \"###;\n\n    let sql = compile(query).unwrap();\n    assert_snapshot!(sql,\n        @r\"\n    WITH e AS (\n      SELECT\n        *\n      FROM\n        employees\n      LIMIT\n        4\n    )\n    SELECT\n      *\n    FROM\n      e\n    \"\n    );\n}\n\n#[test]\nfn test_prql_to_sql_1() {\n    assert_snapshot!(compile(r#\"\n    from employees\n    aggregate {\n        count salary,\n        sum salary,\n    }\n    \"#).unwrap(), @r\"\n    SELECT\n      COUNT(*),\n      COALESCE(SUM(salary), 0)\n    FROM\n      employees\n    \"\n    );\n    assert_snapshot!(compile(r#\"\n    prql target:sql.postgres\n    from developers\n    group team (\n        aggregate {\n            skill_width = count_distinct specialty,\n        }\n    )\n    \"#).unwrap(), @r\"\n    SELECT\n      team,\n      COUNT(DISTINCT specialty) AS skill_width\n    FROM\n      developers\n    GROUP BY\n      team\n    \"\n    )\n}\n\n#[test]\n#[ignore]\nfn test_prql_to_sql_2() {\n    let query = r#\"\nfrom employees\nfilter country == \"USA\"                           # Each line transforms the previous result.\nderive {                                         # This adds columns / variables.\ngross_salary = salary + payroll_tax,\ngross_cost = gross_salary + benefits_cost      # Variables can use other variables.\n}\nfilter gross_cost > 0\ngroup {title, country} (\naggregate  {                                 # `by` are the columns to group by.\n    average salary,                          # These are aggregation calcs run on each group.\n    sum     salary,\n    average gross_salary,\n    sum     gross_salary,\n    average gross_cost,\n    sum_gross_cost = sum gross_cost,\n    ct = count salary,\n}\n)\nsort sum_gross_cost\nfilter ct > 200\ntake 20\n\"#;\n\n    let sql = compile(query).unwrap();\n    assert_snapshot!(sql, @r###\"\n    WITH table_0 AS (\n      SELECT\n        title,\n        country,\n        salary + payroll_tax + benefits_cost AS _expr_0,\n        salary + payroll_tax AS _expr_1,\n        salary\n      FROM\n        employees\n      WHERE\n        country = 'USA'\n    )\n    SELECT\n      title,\n      country,\n      AVG(salary),\n      COALESCE(SUM(salary), 0),\n      AVG(_expr_1),\n      COALESCE(SUM(_expr_1), 0),\n      AVG(_expr_0),\n      COALESCE(SUM(_expr_0), 0) AS sum_gross_cost,\n      COUNT(*) AS ct\n    FROM\n      table_0\n    WHERE\n      _expr_0 > 0\n    GROUP BY\n      title,\n      country\n    HAVING\n      COUNT(*) > 200\n    ORDER BY\n      sum_gross_cost\n    LIMIT\n      20\n    \"###);\n}\n\n#[test]\nfn test_prql_to_sql_table() {\n    // table\n    let query = r#\"\n    let newest_employees = (\n        from employees\n        sort tenure\n        take 50\n    )\n    let average_salaries = (\n        from salaries\n        group country (\n            aggregate {\n                average_country_salary = average salary\n            }\n        )\n    )\n    from newest_employees\n    join average_salaries (==country)\n    select {name, salary, average_country_salary}\n    \"#;\n    let sql = compile(query).unwrap();\n    assert_snapshot!(sql,\n        @\"\n    WITH newest_employees AS (\n      SELECT\n        *\n      FROM\n        employees\n      ORDER BY\n        tenure\n      LIMIT\n        50\n    ), average_salaries AS (\n      SELECT\n        country,\n        AVG(salary) AS average_country_salary\n      FROM\n        salaries\n      GROUP BY\n        country\n    )\n    SELECT\n      newest_employees.name,\n      newest_employees.salary,\n      average_salaries.average_country_salary\n    FROM\n      newest_employees\n      INNER JOIN average_salaries ON newest_employees.country = average_salaries.country\n    ORDER BY\n      employees.tenure\n    \"\n    );\n}\n\n#[test]\nfn test_nonatomic() {\n    // A take, then two aggregates\n    let query = r#\"\n        from employees\n        take 20\n        filter country == \"USA\"\n        group {title, country} (\n            aggregate {\n                salary = average salary\n            }\n        )\n        group {title, country} (\n            aggregate {\n                sum_gross_cost = average salary\n            }\n        )\n        sort sum_gross_cost\n    \"#;\n\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    WITH table_1 AS (\n      SELECT\n        title,\n        country,\n        salary\n      FROM\n        employees\n      LIMIT\n        20\n    ), table_0 AS (\n      SELECT\n        title,\n        country,\n        AVG(salary) AS _expr_0\n      FROM\n        table_1\n      WHERE\n        country = 'USA'\n      GROUP BY\n        title,\n        country\n    )\n    SELECT\n      title,\n      country,\n      AVG(_expr_0) AS sum_gross_cost\n    FROM\n      table_0\n    GROUP BY\n      title,\n      country\n    ORDER BY\n      sum_gross_cost\n    \");\n\n    // A aggregate, then sort and filter\n    let query = r###\"\n        from employees\n        group {title, country} (\n            aggregate {\n                sum_gross_cost = average salary\n            }\n        )\n        sort sum_gross_cost\n        filter sum_gross_cost > 0\n    \"###;\n\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    SELECT\n      title,\n      country,\n      AVG(salary) AS sum_gross_cost\n    FROM\n      employees\n    GROUP BY\n      title,\n      country\n    HAVING\n      AVG(salary) > 0\n    ORDER BY\n      sum_gross_cost\n    \");\n}\n\n#[test]\n/// Confirm a nonatomic table works.\nfn test_nonatomic_table() {\n    // A take, then two aggregates\n    let query = r#\"\n    let a = (\n        from employees\n        take 50\n        group country (aggregate {s\"count(*)\"})\n    )\n    from a\n    join b (==country)\n    select {name, salary, average_country_salary}\n\"#;\n\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    WITH table_0 AS (\n      SELECT\n        country\n      FROM\n        employees\n      LIMIT\n        50\n    ), a AS (\n      SELECT\n        country,\n        count(*)\n      FROM\n        table_0\n      GROUP BY\n        country\n    )\n    SELECT\n      b.name,\n      b.salary,\n      b.average_country_salary\n    FROM\n      a\n      INNER JOIN b ON a.country = b.country\n    \");\n}\n\n#[test]\nfn test_table_names_between_splits_01() {\n    assert_snapshot!(compile(r###\"\n    from employees\n    join d = department (==dept_no)\n    take 10\n    derive emp_no = employees.emp_no\n    join s = salaries (==emp_no)\n    select {employees.emp_no, d.name, s.salary}\n    \"###).unwrap(), @r\"\n    WITH table_0 AS (\n      SELECT\n        employees.emp_no,\n        d.name\n      FROM\n        employees\n        INNER JOIN department AS d ON employees.dept_no = d.dept_no\n      LIMIT\n        10\n    )\n    SELECT\n      table_0.emp_no,\n      table_0.name,\n      s.salary\n    FROM\n      table_0\n      INNER JOIN salaries AS s ON table_0.emp_no = s.emp_no\n    \");\n}\n\n#[test]\nfn test_table_names_between_splits_02() {\n    assert_snapshot!(compile(r###\"\n    from e = employees\n    take 10\n    join salaries (==emp_no)\n    select {e.*, salaries.salary}\n    \"###).unwrap(), @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        employees AS e\n      LIMIT\n        10\n    )\n    SELECT\n      table_0.*,\n      salaries.salary\n    FROM\n      table_0\n      INNER JOIN salaries ON table_0.emp_no = salaries.emp_no\n    \");\n}\n\n#[test]\nfn test_table_alias_01() {\n    assert_snapshot!((compile(r###\"\n    from e = employees\n    join salaries side:left (salaries.emp_no == e.emp_no)\n    group {e.emp_no} (\n        aggregate {\n            emp_salary = average salaries.salary\n        }\n    )\n    select {emp_no, emp_salary}\n    \"###).unwrap()), @r\"\n    SELECT\n      e.emp_no,\n      AVG(salaries.salary) AS emp_salary\n    FROM\n      employees AS e\n      LEFT OUTER JOIN salaries ON salaries.emp_no = e.emp_no\n    GROUP BY\n      e.emp_no\n    \");\n}\n\n#[test]\nfn test_table_alias_02() {\n    assert_snapshot!((compile(r#\"\n    from e = employees\n    select e.first_name\n    filter e.first_name == \"Fred\"\n    \"#).unwrap()), @r\"\n    SELECT\n      first_name\n    FROM\n      employees AS e\n    WHERE\n      first_name = 'Fred'\n    \");\n}\n\n#[test]\nfn test_targets() {\n    // Generic\n    let query = r###\"\n    prql target:sql.generic\n    from Employees\n    select {FirstName, `last name`}\n    take 3\n    \"###;\n\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      \"FirstName\",\n      \"last name\"\n    FROM\n      \"Employees\"\n    LIMIT\n      3\n    \"#);\n\n    // SQL server\n    let query = r###\"\n    prql target:sql.mssql\n    from Employees\n    select {FirstName, `last name`}\n    take 3\n    \"###;\n\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      \"FirstName\",\n      \"last name\"\n    FROM\n      \"Employees\"\n    ORDER BY\n      (\n        SELECT\n          NULL\n      ) OFFSET 0 ROWS\n    FETCH FIRST\n      3 ROWS ONLY\n    \"#);\n\n    // MySQL\n    let query = r###\"\n    prql target:sql.mysql\n    from Employees\n    select {FirstName, `last name`}\n    take 3\n    \"###;\n\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    SELECT\n      `FirstName`,\n      `last name`\n    FROM\n      `Employees`\n    LIMIT\n      3\n    \");\n}\n\n#[test]\nfn test_target_clickhouse() {\n    let query = r###\"\n    prql target:sql.clickhouse\n\n    from github_json\n    derive {event_type_dotted = `event.type`}\n    \"###;\n\n    assert_snapshot!((compile(query).unwrap()), @r\"\n    SELECT\n      *,\n      `event.type` AS event_type_dotted\n    FROM\n      github_json\n    \");\n}\n\n#[test]\nfn test_ident_escaping() {\n    // Generic\n    let query = r#\"\n    from `anim\"ls`\n    derive {`čebela` = BeeName, medved = `bear's_name`}\n    \"#;\n\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      *,\n      \"BeeName\" AS \"čebela\",\n      \"bear's_name\" AS medved\n    FROM\n      \"anim\"\"ls\"\n    \"#);\n\n    // MySQL\n    let query = r#\"\n    prql target:sql.mysql\n\n    from `anim\"ls`\n    derive {`čebela` = BeeName, medved = `bear's_name`}\n    \"#;\n\n    assert_snapshot!((compile(query).unwrap()), @r#\"\n    SELECT\n      *,\n      `BeeName` AS `čebela`,\n      `bear's_name` AS medved\n    FROM\n      `anim\"ls`\n    \"#);\n}\n\n#[test]\nfn test_literal() {\n    let query = r###\"\n    from employees\n    derive {always_true = true}\n    \"###;\n\n    let sql = compile(query).unwrap();\n    assert_snapshot!(sql,\n        @r\"\n    SELECT\n      *,\n      true AS always_true\n    FROM\n      employees\n    \"\n    );\n}\n\n#[test]\nfn test_same_column_names() {\n    // #820\n    let query = r###\"\nlet x = (\nfrom x_table\nselect only_in_x = foo\n)\n\nlet y = (\nfrom y_table\nselect foo\n)\n\nfrom x\njoin y (foo == only_in_x)\n\"###;\n\n    assert_snapshot!(compile(query).unwrap(),\n        @r\"\n    WITH x AS (\n      SELECT\n        foo AS only_in_x\n      FROM\n        x_table\n    ),\n    y AS (\n      SELECT\n        foo\n      FROM\n        y_table\n    )\n    SELECT\n      x.only_in_x,\n      y.foo\n    FROM\n      x\n      INNER JOIN y ON y.foo = x.only_in_x\n    \"\n    );\n}\n\n#[test]\nfn test_double_aggregate() {\n    // #941\n    compile(\n        r###\"\n    from numbers\n    group {type} (\n        aggregate {\n            total_amt = sum amount,\n        }\n        aggregate {\n            max amount\n        }\n    )\n    \"###,\n    )\n    .unwrap_err();\n\n    assert_snapshot!(compile(r###\"\n    from numbers\n    group {`type`} (\n        aggregate {\n            total_amt = sum amount,\n            max amount\n        }\n    )\n    \"###).unwrap(),\n        @r\"\n    SELECT\n      type,\n      COALESCE(SUM(amount), 0) AS total_amt,\n      MAX(amount)\n    FROM\n      numbers\n    GROUP BY\n      type\n    \"\n    );\n}\n\n#[test]\nfn test_window_function_coalesce() {\n    // #3587\n    assert_snapshot!(compile(r###\"\n    from x\n    select {a, b=a}\n    window (\n      select {\n        cumsum_a=(sum a),\n        cumsum_b=(sum b)\n      }\n    )\n    \"###).unwrap(),\n        @r\"\n    SELECT\n      SUM(a) OVER () AS cumsum_a,\n      SUM(a) OVER () AS cumsum_b\n    FROM\n      x\n    \"\n    );\n}\n\n#[test]\nfn test_casting() {\n    assert_snapshot!(compile(r###\"\n    from x\n    select {a}\n    derive {\n        b = (a | as int) + 10,\n        c = (a | as int) - 10,\n        d = (a | as float) * 10,\n        e = (a | as float) / 10,\n    }\n    \"###).unwrap(),\n        @r\"\n    SELECT\n      a,\n      CAST(a AS int) + 10 AS b,\n      CAST(a AS int) - 10 AS c,\n      CAST(a AS float) * 10 AS d,\n      CAST(a AS float) / 10 AS e\n    FROM\n      x\n    \"\n    );\n}\n\n#[test]\nfn test_toposort() {\n    // #1183\n\n    assert_snapshot!(compile(r###\"\n    let b = (\n        from somesource\n    )\n\n    let a = (\n        from b\n    )\n\n    from b\n    \"###).unwrap(),\n        @r\"\n    WITH b AS (\n      SELECT\n        *\n      FROM\n        somesource\n    )\n    SELECT\n      *\n    FROM\n      b\n    \"\n    );\n}\n\n#[test]\nfn test_inline_tables() {\n    assert_snapshot!(compile(r###\"\n    (\n        from employees\n        select {emp_id, name, surname, `type`, amount}\n    )\n    join s = (from salaries | select {emp_id, salary}) (==emp_id)\n    \"###).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        emp_id,\n        salary\n      FROM\n        salaries\n    )\n    SELECT\n      employees.emp_id,\n      employees.name,\n      employees.surname,\n      employees.type,\n      employees.amount,\n      table_0.emp_id,\n      table_0.salary\n    FROM\n      employees\n      INNER JOIN table_0 ON employees.emp_id = table_0.emp_id\n    \"\n    );\n}\n\n#[test]\nfn test_filter_and_select_unchanged_alias() {\n    // #1185\n\n    assert_snapshot!(compile(r###\"\n    from account\n    filter account.name != null\n    select {name = account.name}\n    \"###).unwrap(),\n        @r\"\n    SELECT\n      name\n    FROM\n      account\n    WHERE\n      name IS NOT NULL\n    \"\n    );\n}\n\n#[test]\nfn test_filter_and_select_changed_alias() {\n    // #1185\n    assert_snapshot!(compile(r###\"\n    from account\n    filter account.name != null\n    select {renamed_name = account.name}\n    \"###).unwrap(),\n        @r\"\n    SELECT\n      name AS renamed_name\n    FROM\n      account\n    WHERE\n      name IS NOT NULL\n    \"\n    );\n\n    // #1207\n    assert_snapshot!(compile(r#\"\n    from x\n    filter name != \"Bob\"\n    select name = name ?? \"Default\"\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      COALESCE(name, 'Default') AS name\n    FROM\n      x\n    WHERE\n      name <> 'Bob'\n    \"\n    );\n}\n\n#[test]\nfn test_unused_alias() {\n    // #1308\n    assert_snapshot!(compile(r###\"\n    from account\n    select n = {account.name}\n    \"###).unwrap_err(), @r\"\n    Error:\n       ╭─[ :3:16 ]\n       │\n     3 │     select n = {account.name}\n       │                ───────┬──────\n       │                       ╰──────── unexpected assign to `n`\n       │\n       │ Help: move assign into the tuple: `[n = ...]`\n    ───╯\n    \")\n}\n\n#[test]\nfn test_table_s_string_01() {\n    assert_snapshot!(compile(r#\"\n    let main <relation> = s\"SELECT DISTINCT ON first_name, age FROM employees ORDER BY age ASC\"\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        DISTINCT ON first_name,\n        age\n      FROM\n        employees\n      ORDER BY\n        age ASC\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n#[test]\nfn test_table_s_string_02() {\n    assert_snapshot!(compile(r#\"\n    s\"\"\"\n        SELECT DISTINCT ON first_name, id, age FROM employees ORDER BY age ASC\n    \"\"\"\n    join s = s\"SELECT * FROM salaries\" (==id)\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        DISTINCT ON first_name,\n        id,\n        age\n      FROM\n        employees\n      ORDER BY\n        age ASC\n    ),\n    table_1 AS (\n      SELECT\n        *\n      FROM\n        salaries\n    )\n    SELECT\n      table_0.*,\n      table_1.*\n    FROM\n      table_0\n      INNER JOIN table_1 ON table_0.id = table_1.id\n    \"\n    );\n}\n#[test]\nfn test_table_s_string_03() {\n    assert_snapshot!(compile(r#\"\n    s\"\"\"SELECT * FROM employees\"\"\"\n    filter country == \"USA\"\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        employees\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      country = 'USA'\n    \"\n    );\n}\n#[test]\nfn test_table_s_string_04() {\n    assert_snapshot!(compile(r#\"\n    s\"\"\"SELECT * FROM employees\"\"\"\n    select {e = this}\n    filter e.country == \"USA\"\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        employees\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      country = 'USA'\n    \"\n    );\n}\n#[test]\nfn test_table_s_string_05() {\n    assert_snapshot!(compile(r#\"\n    let weeks_between = start end -> s\"SELECT generate_series({start}, {end}, '1 week') as date\"\n    let current_week = -> s\"date(date_trunc('week', current_date))\"\n\n    weeks_between @2022-06-03 (current_week + 4)\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        generate_series(\n          DATE '2022-06-03',\n          date(date_trunc('week', current_date)) + 4,\n          '1 week'\n        ) as date\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n#[test]\nfn test_table_s_string_06() {\n    assert_snapshot!(compile(r#\"\n    s\"SELECT * FROM {default_db.x}\"\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        x\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_direct_table_references() {\n    assert_snapshot!(compile(\n        r#\"\n    from x\n    select s\"{x}.field\"\n    \"#,\n    )\n    .unwrap_err(), @r#\"\n    Error:\n       ╭─[ :3:15 ]\n       │\n     3 │     select s\"{x}.field\"\n       │               ┬\n       │               ╰── table instance cannot be referenced directly\n       │\n       │ Help: column name might be missing?\n    ───╯\n    \"#);\n\n    assert_snapshot!(compile(\n        r###\"\n    from x\n    select x\n    \"###,\n    )\n    .unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      x\n    \");\n}\n\n#[test]\nfn test_table_variable_in_scalar_context() {\n    // https://github.com/PRQL/prql/issues/5158\n    assert_snapshot!(compile(\n        r#\"\n    let mod_id = (from users | filter login == \"nightpool\" | select id | take 1)\n\n    from modlog\n    filter actor_id == mod_id\n    \"#,\n    )\n    .unwrap_err(), @r#\"\n    Error:\n       ╭─[ :5:24 ]\n       │\n     5 │     filter actor_id == mod_id\n       │                        ───┬──\n       │                           ╰──── table variable cannot be used as a scalar value\n       │\n       │ Help: use a join instead, or inline the subquery\n    ───╯\n    \"#);\n}\n\n#[test]\nfn test_name_shadowing() {\n    assert_snapshot!(compile(\n        r###\"\n    from x\n    select {a, a, a = a + 1}\n    \"###).unwrap(),\n        @r\"\n    SELECT\n      a AS _expr_0,\n      a + 1 AS a\n    FROM\n      x\n    \"\n    );\n\n    assert_snapshot!(compile(\n        r###\"\n    from x\n    select a\n    derive a\n    derive a = a + 1\n    derive a = a + 2\n    \"###).unwrap(),\n        @r\"\n    SELECT\n      a AS _expr_0,\n      a + 1,\n      a + 1 + 2 AS a\n    FROM\n      x\n    \"\n    );\n}\n\n#[test]\nfn test_group_all() {\n    assert_snapshot!(compile(\n        r###\"\n    prql target:sql.sqlite\n\n    from a=albums\n    group a.* (aggregate {count this})\n        \"###).unwrap_err(), @\"Error: Target dialect does not support * in this position.\");\n\n    assert_snapshot!(compile(\n        r###\"\n    from e=albums\n    group !{genre_id} (aggregate {count this})\n        \"###).unwrap_err(), @\"Error: Excluding columns not supported as this position\");\n}\n\n#[test]\nfn test_output_column_deduplication() {\n    // #1249\n    assert_snapshot!(compile(\n        r#\"\n    from report\n    derive r = s\"RANK() OVER ()\"\n    filter r == 1\n        \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *,\n        RANK() OVER () AS r\n      FROM\n        report\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      r = 1\n    \"\n    );\n}\n\n#[test]\nfn test_case_01() {\n    assert_snapshot!(compile(\n        r###\"\n    from employees\n    derive display_name = case [\n        nickname != null => nickname,\n        true => f'{first_name} {last_name}'\n    ]\n        \"###).unwrap(),\n        @r\"\n    SELECT\n      *,\n      CASE\n        WHEN nickname IS NOT NULL THEN nickname\n        ELSE CONCAT(first_name, ' ', last_name)\n      END AS display_name\n    FROM\n      employees\n    \"\n    );\n}\n\n#[test]\nfn test_case_02() {\n    assert_snapshot!(compile(\n        r###\"\n    from employees\n    derive display_name = case [\n        nickname != null => nickname,\n        first_name != null => f'{first_name} {last_name}'\n    ]\n        \"###).unwrap(),\n        @r\"\n    SELECT\n      *,\n      CASE\n        WHEN nickname IS NOT NULL THEN nickname\n        WHEN first_name IS NOT NULL THEN CONCAT(first_name, ' ', last_name)\n        ELSE NULL\n      END AS display_name\n    FROM\n      employees\n    \"\n    );\n}\n\n#[test]\nfn test_case_03() {\n    assert_snapshot!(compile(\n        r###\"\n    from tracks\n    select category = case [\n        length > avg_length => 'long'\n    ]\n    group category (aggregate {count this})\n        \"###).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        CASE\n          WHEN length > avg_length THEN 'long'\n          ELSE NULL\n        END AS category,\n        length,\n        avg_length\n      FROM\n        tracks\n    )\n    SELECT\n      category,\n      COUNT(*)\n    FROM\n      table_0\n    GROUP BY\n      category\n    \"\n    );\n}\n\n#[test]\nfn test_sql_options() {\n    let options = Options::default();\n    let sql = prqlc::compile(\"from x\", &options).unwrap();\n\n    assert!(sql.contains('\\n'));\n    assert!(sql.contains(\"-- Generated by\"));\n\n    let options = Options::default().no_signature().no_format();\n    let sql = prqlc::compile(\"from x\", &options).unwrap();\n\n    assert!(!sql.contains('\\n'));\n    assert!(!sql.contains(\"-- Generated by\"));\n}\n\n#[test]\nfn test_static_analysis() {\n    assert_snapshot!(compile(\n        r###\"\n    from x\n    select {\n        a = (- (-3)),\n        b = !(!(!(!(!(true))))),\n        a3 = null ?? y,\n\n        a3 = case [\n            false == true => 1,\n            7 == 3 => 2,\n            7 == y => 3,\n            7.3 == 7.3 => 4,\n            z => 5,\n            true => 6\n        ],\n    }\n        \"###).unwrap(),\n        @r\"\n    SELECT\n      3 AS a,\n      false AS b,\n      y AS _expr_0,\n      CASE\n        WHEN 7 = y THEN 3\n        ELSE 4\n      END AS a3\n    FROM\n      x\n    \"\n    );\n}\n\n#[test]\nfn test_closures_and_pipelines() {\n    assert_snapshot!(compile(\n        r#\"\n    let addthree = a b c -> s\"{a} || {b} || {c}\"\n    let arg = myarg myfunc <func> -> ( myfunc myarg )\n\n    from y\n    select x = (\n        addthree \"apples\"\n        arg \"bananas\"\n        arg \"citrus\"\n    )\n        \"#).unwrap(),\n        @r\"\n    SELECT\n      'apples' || 'bananas' || 'citrus' AS x\n    FROM\n      y\n    \"\n    );\n}\n\n#[test]\nfn test_basic_agg() {\n    assert_snapshot!(compile(r#\"\n    from employees\n    aggregate {\n      count salary,\n      count this,\n    }\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      COUNT(*),\n      COUNT(*)\n    FROM\n      employees\n    \"\n    );\n}\n\n#[test]\nfn test_exclude_columns_01() {\n    assert_snapshot!(compile(r#\"\n    from tracks\n    select {track_id, title, composer, bytes}\n    select !{title, composer}\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      track_id,\n      bytes\n    FROM\n      tracks\n    \"\n    );\n}\n\n#[test]\nfn test_exclude_columns_02() {\n    assert_snapshot!(compile(r#\"\n    from tracks\n    select {track_id, title, composer, bytes}\n    group !{title, composer} (aggregate {count this})\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      track_id,\n      bytes,\n      COUNT(*)\n    FROM\n      tracks\n    GROUP BY\n      track_id,\n      bytes\n    \"\n    );\n}\n\n#[test]\nfn test_exclude_columns_03() {\n    assert_snapshot!(compile(r#\"\n    from artists\n    derive nick = name\n    select !{artists.*}\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      name AS nick\n    FROM\n      artists\n    \"\n    );\n}\n\n#[test]\nfn test_exclude_columns_04() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.bigquery\n    from tracks\n    select !{milliseconds,bytes}\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      * EXCEPT (milliseconds, bytes)\n    FROM\n      tracks\n    \"\n    );\n}\n\n#[test]\nfn test_exclude_columns_05() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.snowflake\n    from tracks\n    select !{milliseconds,bytes}\n    \"#).unwrap(),\n        @r#\"\n    SELECT\n      * EXCLUDE (\"milliseconds\", \"bytes\")\n    FROM\n      \"tracks\"\n    \"#\n    );\n}\n\n#[test]\nfn test_exclude_columns_06() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.duckdb\n    from tracks\n    select !{milliseconds,bytes}\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      * EXCLUDE (milliseconds, bytes)\n    FROM\n      tracks\n    \"\n    );\n}\n\n#[test]\nfn test_exclude_columns_07() {\n    assert_snapshot!(compile(r#\"\n    prql target:sql.duckdb\n    from s\"SELECT * FROM foo\"\n    select !{bar}\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        foo\n    )\n    SELECT\n      * EXCLUDE (bar)\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_custom_transforms() {\n    assert_snapshot!(compile(r#\"\n    let my_transform = (\n        derive double = single * 2\n        sort name\n    )\n\n    from tab\n    my_transform\n    take 3\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      *,\n      single * 2 AS double\n    FROM\n      tab\n    ORDER BY\n      name\n    LIMIT\n      3\n    \"\n    );\n}\n\n#[test]\nfn test_name_inference() {\n    assert_snapshot!(compile(r#\"\n    from albums\n    select {artist_id + album_id}\n    # nothing inferred infer\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      artist_id + album_id\n    FROM\n      albums\n    \"\n    );\n\n    let sql1 = compile(\n        r#\"\n    from albums\n    select {artist_id}\n    select {albums.artist_id}\n    \"#,\n    )\n    .unwrap();\n    let sql2 = compile(\n        r#\"\n    from albums\n    select {albums.artist_id}\n    select {albums.artist_id}\n    \"#,\n    )\n    .unwrap();\n    assert_eq!(sql1, sql2);\n\n    assert_snapshot!(\n        sql1,\n        @r\"\n    SELECT\n      artist_id\n    FROM\n      albums\n    \"\n    );\n}\n\n#[test]\nfn test_from_text_01() {\n    assert_snapshot!(compile(r#\"\n    from_text format:csv \"\"\"\na,b,c\n1,2,3\n4,5,6\n    \"\"\"\n    select {b, c}\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        '1' AS a,\n        '2' AS b,\n        '3' AS c\n      UNION\n      ALL\n      SELECT\n        '4' AS a,\n        '5' AS b,\n        '6' AS c\n    )\n    SELECT\n      b,\n      c\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_from_text_02() {\n    assert_snapshot!(compile(r#\"\n    from_text format:json '''\n      [{\"a\": 1, \"b\": \"x\", \"c\": false }, {\"a\": 4, \"b\": \"y\", \"c\": null }]\n    '''\n    select {b, c}\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        1 AS a,\n        'x' AS b,\n        false AS c\n      UNION\n      ALL\n      SELECT\n        4 AS a,\n        'y' AS b,\n        NULL AS c\n    )\n    SELECT\n      b,\n      c\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_from_text_03() {\n    assert_snapshot!(compile(r#\"\n    std.from_text format:json '''{\n        \"columns\": [\"a\", \"b\", \"c\"],\n        \"data\": [\n            [1, \"x\", false],\n            [4, \"y\", null]\n        ]\n    }'''\n    select {b, c}\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        1 AS a,\n        'x' AS b,\n        false AS c\n      UNION\n      ALL\n      SELECT\n        4 AS a,\n        'y' AS b,\n        NULL AS c\n    )\n    SELECT\n      b,\n      c\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_from_text_04() {\n    assert_snapshot!(compile(r#\"\n    std.from_text 'a,b'\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        NULL AS a,\n        NULL AS b\n      WHERE\n        false\n    )\n    SELECT\n      a,\n      b\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_from_text_05() {\n    assert_snapshot!(compile(r#\"\n    std.from_text format:json '''{\"columns\": [\"a\", \"b\", \"c\"], \"data\": []}'''\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        NULL AS a,\n        NULL AS b,\n        NULL AS c\n      WHERE\n        false\n    )\n    SELECT\n      a,\n      b,\n      c\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_from_text_06() {\n    assert_snapshot!(compile(r#\"\n    std.from_text ''\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        NULL\n      WHERE\n        false\n    )\n    SELECT\n      NULL\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_from_text_07() {\n    assert_snapshot!(compile(r#\"\n    std.from_text format:json '''{\"columns\": [], \"data\": [[], []]}'''\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n      UNION\n      ALL\n      SELECT\n    )\n    SELECT\n      NULL\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_header() {\n    // Test both target & version at the same time\n    let header = format!(\n        r#\"\n            prql target:sql.mssql version:\"{}.{}\"\n            \"#,\n        env!(\"CARGO_PKG_VERSION_MAJOR\"),\n        env!(\"CARGO_PKG_VERSION_MINOR\")\n    );\n    assert_snapshot!(compile(format!(r#\"\n    {header}\n\n    from a\n    take 5\n    \"#).as_str()).unwrap(),@r\"\n    SELECT\n      *\n    FROM\n      a\n    ORDER BY\n      (\n        SELECT\n          NULL\n      ) OFFSET 0 ROWS\n    FETCH FIRST\n      5 ROWS ONLY\n    \");\n}\n#[test]\nfn test_header_target_error() {\n    assert_snapshot!(compile(r#\"\n    prql target:foo\n    from a\n    \"#).unwrap_err(),@r#\"Error: target `\"foo\"` not found\"#);\n\n    assert_snapshot!(compile(r#\"\n    prql target:sql.foo\n    from a\n    \"#).unwrap_err(),@r#\"Error: target `\"sql.foo\"` not found\"#);\n\n    assert_snapshot!(compile(r#\"\n    prql target:foo.bar\n    from a\n    \"#).unwrap_err(),@r#\"Error: target `\"foo.bar\"` not found\"#);\n\n    // TODO: Can we use the span of:\n    // - Ideally just `dialect`?\n    // - At least not the first empty line?\n    assert_snapshot!(compile(r#\"\n    prql dialect:foo.bar\n    from a\n    \"#).unwrap_err(),@r\"\n    Error:\n       ╭─[ :1:1 ]\n       │\n     1 │ ╭─▶\n     2 │ ├─▶     prql dialect:foo.bar\n       │ │\n       │ ╰────────────────────────────── unknown query definition arguments `dialect`\n    ───╯\n    \");\n}\n\n#[test]\nfn shortest_prql_version() {\n    let mut escape_version = insta::Settings::new();\n    escape_version.add_filter(r\"'.*'\", \"[VERSION]\");\n    escape_version.bind(|| {\n        assert_snapshot!(compile(r#\"[{version = prql.version}]\"#).unwrap(),@r\"\n        WITH table_0 AS (\n          SELECT\n            [VERSION] AS version\n        )\n        SELECT\n          version\n        FROM\n          table_0\n        \");\n\n        assert_snapshot!(compile(r#\"\n    from x\n    derive y = std.prql.version\n    \"#).unwrap(),@r\"\n        SELECT\n          *,\n          [VERSION] AS y\n        FROM\n          x\n        \");\n    })\n}\n\n#[test]\nfn test_loop() {\n    assert_snapshot!(compile(r#\"\n    [{n = 1}]\n    select n = n - 2\n    loop (\n        select n = n+1\n        filter n<5\n    )\n    select n = n * 2\n    take 4\n    \"#).unwrap(),\n        @r\"\n    WITH RECURSIVE table_0 AS (\n      SELECT\n        1 AS n\n    ),\n    table_1 AS (\n      SELECT\n        n - 2 AS _expr_0\n      FROM\n        table_0\n      UNION\n      ALL\n      SELECT\n        _expr_1\n      FROM\n        (\n          SELECT\n            _expr_0 + 1 AS _expr_1\n          FROM\n            table_1\n        ) AS table_4\n      WHERE\n        _expr_1 < 5\n    )\n    SELECT\n      _expr_0 * 2 AS n\n    FROM\n      table_1 AS table_3\n    LIMIT\n      4\n    \"\n    );\n}\n\n#[test]\nfn test_loop_2() {\n    assert_snapshot!(compile(r#\"\n    read_csv 'employees.csv'\n    filter last_name==\"Mitchell\"\n    loop (\n      join manager=employees (manager.employee_id==this.reports_to)\n      select manager.*\n    )\n    \"#).unwrap(),\n        @r\"\n    WITH RECURSIVE table_0 AS (\n      SELECT\n        *\n      FROM\n        read_csv('employees.csv')\n    ),\n    table_1 AS (\n      SELECT\n        *\n      FROM\n        table_0\n      WHERE\n        last_name = 'Mitchell'\n      UNION\n      ALL\n      SELECT\n        manager.*\n      FROM\n        table_1\n        INNER JOIN employees AS manager ON manager.employee_id = table_1.reports_to\n    )\n    SELECT\n      *\n    FROM\n      table_1 AS table_2\n    \"\n    );\n}\n\n#[test]\nfn test_params() {\n    assert_snapshot!(compile(r#\"\n    from invoices\n    select {i = this}\n    filter $1 <= i.date || i.date <= $2\n    select {\n        i.id,\n        i.total,\n    }\n    filter i.total > $3\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      id,\n      total\n    FROM\n      invoices\n    WHERE\n      (\n        $1 <= date\n        OR date <= $2\n      )\n      AND total > $3\n    \"\n    )\n}\n\n// for #1969\n#[test]\nfn test_datetime() {\n    let query = &r#\"\n        from test_table\n        select {date = @2022-12-31, time = @08:30, timestamp = @2020-01-01T13:19:55-0800}\n        \"#;\n\n    assert_snapshot!(\n                compile(query).unwrap(),\n                @r\"\n    SELECT\n      DATE '2022-12-31' AS date,\n      TIME '08:30' AS time,\n      TIMESTAMP '2020-01-01T13:19:55-0800' AS timestamp\n    FROM\n      test_table\n    \"\n    )\n}\n\n#[test]\nfn test_datetime_sqlite() {\n    // for #1969\n\n    assert_snapshot!(compile(r#\"\n    prql target:sql.sqlite\n\n    from x\n    select {\n        date = @2022-12-31,\n        time = @08:30,\n        time_tz = @03:05+08:00,\n        time_tz2 = @03:05+0800,\n        timestamp1 = @2020-01-01T13:19:55-0800,\n        timestamp2 = @2021-03-14T03:05+0800,\n        timestamp3 = @2021-03-14T03:05+08:00,\n    }\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      DATE('2022-12-31') AS date,\n      TIME('08:30') AS time,\n      TIME('03:05+08:00') AS time_tz,\n      TIME('03:05+08:00') AS time_tz2,\n      DATETIME('2020-01-01T13:19:55-08:00') AS timestamp1,\n      DATETIME('2021-03-14T03:05+08:00') AS timestamp2,\n      DATETIME('2021-03-14T03:05+08:00') AS timestamp3\n    FROM\n      x\n    \"\n    );\n}\n\n#[test]\nfn test_datetime_parsing() {\n    assert_snapshot!(compile(r#\"\n    from test_tables\n    select {date = @2022-12-31, time = @08:30, timestamp = @2020-01-01T13:19:55-0800}\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      DATE '2022-12-31' AS date,\n      TIME '08:30' AS time,\n      TIMESTAMP '2020-01-01T13:19:55-0800' AS timestamp\n    FROM\n      test_tables\n    \"\n    );\n}\n\n#[test]\nfn test_lower() {\n    assert_snapshot!(compile(r#\"\n    from test_tables\n    derive {lower_name = (name | text.lower)}\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      *,\n      LOWER(name) AS lower_name\n    FROM\n      test_tables\n    \"\n    );\n}\n\n#[test]\nfn test_upper() {\n    assert_snapshot!(compile(r#\"\n    from test_tables\n    derive {upper_name = text.upper name}\n    select {upper_name}\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      UPPER(name) AS upper_name\n    FROM\n      test_tables\n    \"\n    );\n}\n\n#[test]\nfn test_1535() {\n    assert_snapshot!(compile(r#\"\n    from x.y.z\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      *\n    FROM\n      x.y.z\n    \"\n    );\n}\n\n#[test]\nfn test_read_parquet_duckdb() {\n    assert_snapshot!(compile(r#\"\n    std.read_parquet 'x.parquet'\n    join (std.read_parquet \"y.parquet\") (==foo)\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        read_parquet('x.parquet')\n    ),\n    table_1 AS (\n      SELECT\n        *\n      FROM\n        read_parquet('y.parquet')\n    )\n    SELECT\n      table_0.*,\n      table_1.*\n    FROM\n      table_0\n      INNER JOIN table_1 ON table_0.foo = table_1.foo\n    \"\n    );\n\n    // TODO: `from x=(read_parquet 'x.parquet')` currently fails\n}\n\n#[test]\nfn test_read_parquet_with_named_args() {\n    assert_snapshot!(compile_with_sql_dialect(r#\"\n    std.read_parquet 'data.parquet' union_by_name:true\n    \"#, sql::Dialect::DuckDb).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        read_parquet(\n          'data.parquet',\n          binary_as_string = false,\n          file_row_number = false,\n          hive_partitioning = NULL,\n          union_by_name = true\n        )\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n\n    assert_snapshot!(compile_with_sql_dialect(r#\"\n    std.read_parquet 'data.parquet' union_by_name:true binary_as_string:true\n    \"#, sql::Dialect::DuckDb).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        read_parquet(\n          'data.parquet',\n          binary_as_string = true,\n          file_row_number = false,\n          hive_partitioning = NULL,\n          union_by_name = true\n        )\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_read_json_duckdb() {\n    assert_snapshot!(compile_with_sql_dialect(r#\"\n    from (read_json 'data.json')\n    \"#, sql::Dialect::DuckDb).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        read_json_auto('data.json')\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_read_json_clickhouse() {\n    assert_snapshot!(compile_with_sql_dialect(r#\"\n    from (read_json 'data.json')\n    \"#, sql::Dialect::ClickHouse).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        file('data.json', 'Json')\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_read_json_generic() {\n    assert_snapshot!(compile(r#\"\n    from (read_json 'data.json')\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        read_json('data.json')\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_excess_columns() {\n    // https://github.com/PRQL/prql/issues/2079\n    assert_snapshot!(compile(r#\"\n    from tracks\n    derive d = track_id\n    sort d\n    select {title}\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        title,\n        track_id AS _expr_0\n      FROM\n        tracks\n    )\n    SELECT\n      title\n    FROM\n      table_0\n    ORDER BY\n      _expr_0\n    \"\n    );\n}\n\n#[test]\nfn test_regex_search() {\n    assert_snapshot!(compile(r#\"\n    from tracks\n    derive is_bob_marley = artist_name ~= \"Bob\\\\sMarley\"\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      *,\n      REGEXP(artist_name, 'Bob\\sMarley') AS is_bob_marley\n    FROM\n      tracks\n    \"\n    );\n}\n\n#[test]\nfn test_intervals() {\n    assert_snapshot!(compile(r#\"\n    from foo\n    select dt = 1years + 1months + 1weeks + 1days + 1hours + 1minutes + 1seconds + 1milliseconds + 1microseconds\n    \"#).unwrap(),\n        @r\"\n    SELECT\n      INTERVAL 1 YEAR + INTERVAL 1 MONTH + INTERVAL 1 WEEK + INTERVAL 1 DAY + INTERVAL 1 HOUR + INTERVAL 1 MINUTE + INTERVAL 1 SECOND + INTERVAL 1 MILLISECOND + INTERVAL 1 MICROSECOND AS dt\n    FROM\n      foo\n    \"\n    );\n}\n\n#[test]\nfn test_into() {\n    assert_snapshot!(compile(r#\"\n    from data\n    into table_a\n\n    from table_a\n    select {x, y}\n    \"#).unwrap(),\n        @r\"\n    WITH table_a AS (\n      SELECT\n        *\n      FROM\n        data\n    )\n    SELECT\n      x,\n      y\n    FROM\n      table_a\n    \"\n    );\n}\n\n#[test]\nfn test_array_01() {\n    compile(\n        r#\"\n    let a = [1, 2, false]\n\n    from x\n    \"#,\n    )\n    .unwrap();\n\n    assert_snapshot!(compile(r#\"\n    let my_relation = [\n        {a = 3, b = false},\n        {a = 4, b = true},\n    ]\n\n    let main = (my_relation | filter b)\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        3 AS a,\n        false AS b\n      UNION\n      ALL\n      SELECT\n        4 AS a,\n        true AS b\n    ),\n    my_relation AS (\n      SELECT\n        a,\n        b\n      FROM\n        table_0\n    )\n    SELECT\n      a,\n      b\n    FROM\n      my_relation\n    WHERE\n      b\n    \"\n    );\n}\n\n#[test]\nfn test_array_02() {\n    assert_snapshot!(compile(r###\"\n    let x = p1 -> s\"x({p1})\"\n\n    from [{a=null}, {a=2}]\n    filter (a | in [2, 4])\n    select {\n      empty_array = [],\n      single_element = [42],\n      null_element = [null],\n      complex_expressions = [a + a, (a * 2) + 1],\n      nested_function_calls = [(min a), (max a ?? 0)],\n      passing_as_arg = x [1,2,3],\n      nested = ['a', ['b']]\n    }\n    \"###).unwrap(), @r\"\n    WITH table_0 AS (\n      SELECT\n        NULL AS a\n      UNION\n      ALL\n      SELECT\n        2 AS a\n    )\n    SELECT\n      [] AS empty_array,\n      [42] AS single_element,\n      [NULL] AS null_element,\n      [a + a, a * 2 + 1] AS complex_expressions,\n      [MIN(a) OVER (), MAX(COALESCE(a, 0)) OVER ()] AS nested_function_calls,\n      x([1, 2, 3]) AS passing_as_arg,\n      [ 'a',\n      [ 'b' ] ] AS nested\n    FROM\n      table_0\n    WHERE\n      a IN (2, 4)\n    \");\n}\n\n#[test]\nfn test_array_03() {\n    assert_snapshot!(compile(r###\"\n    from employees\n    select {e = this}\n    select [e.first_name, e.last_name]\n    \"###).unwrap(), @r\"\n    SELECT\n      [first_name, last_name]\n    FROM\n      employees\n    \");\n}\n\n#[test]\nfn test_double_stars() {\n    assert_snapshot!(compile(r#\"\n    from tb1\n    join tb2 (==c2)\n    take 5\n    filter (tb2.c3 < 100)\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        tb1.*,\n        tb2.*\n      FROM\n        tb1\n        INNER JOIN tb2 ON tb1.c2 = tb2.c2\n      LIMIT\n        5\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      c3 < 100\n    \"\n    );\n\n    assert_snapshot!(compile(r#\"\n    prql target:sql.duckdb\n\n    from tb1\n    join tb2 (==c2)\n    take 5\n    filter (tb2.c3 < 100)\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        tb1.*,\n        tb2.*\n      FROM\n        tb1\n        INNER JOIN tb2 ON tb1.c2 = tb2.c2\n      LIMIT\n        5\n    )\n    SELECT\n      *\n    FROM\n      table_0\n    WHERE\n      c3 < 100\n    \"\n    );\n}\n\n#[test]\nfn test_lineage() {\n    // #2627\n    assert_snapshot!(compile(r#\"\n    from_text \"\"\"\n    a\n    1\n    2\n    3\n    \"\"\"\n    derive a = a\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        '    1' AS a\n      UNION\n      ALL\n      SELECT\n        '    2' AS a\n      UNION\n      ALL\n      SELECT\n        '    3' AS a\n    )\n    SELECT\n      a\n    FROM\n      table_0\n    \"\n    );\n\n    // #2392\n    assert_snapshot!(compile(r#\"\n    from_text format:json \"\"\"{\n        \"columns\": [\"a\"],\n        \"data\": [[1]]\n    }\"\"\"\n    derive a = a + 1\n    \"#).unwrap(),\n        @r\"\n    WITH table_0 AS (\n      SELECT\n        1 AS a\n    )\n    SELECT\n      a AS _expr_0,\n      a + 1 AS a\n    FROM\n      table_0\n    \"\n    );\n}\n\n#[test]\nfn test_type_as_column_name() {\n    // #2503\n    assert_snapshot!(compile(r#\"\n    let f = tbl -> (\n      t = tbl\n      select t.date\n    )\n\n    from foo\n    f\"#)\n    .unwrap(), @r\"\n    SELECT\n      date\n    FROM\n      foo AS t\n    \");\n}\n\n#[test]\nfn test_error_code() {\n    let err = compile(\n        r###\"\n    let a = (from x)\n    \"###,\n    )\n    .unwrap_err();\n    assert_eq!(err.inner[0].code.as_ref().unwrap(), \"E0001\");\n}\n\n#[test]\nfn large_query() {\n    // This was causing a stack overflow on Windows, ref https://github.com/PRQL/prql/issues/2857\n    compile(\n        r###\"\nfrom employees\nfilter gross_cost > 0\ngroup {title} (\n  aggregate {\n    ct = count this,\n  }\n)\nsort ct\nfilter ct > 200\ntake 20\nsort ct\nfilter ct > 200\ntake 20\nsort ct\nfilter ct > 200\ntake 20\nsort ct\nfilter ct > 200\ntake 20\n    \"###,\n    )\n    .unwrap();\n}\n\n#[test]\nfn test_returning_constants_only() {\n    assert_snapshot!(compile(\n        r###\"\n    from tb1\n    sort {a}\n    select {c = b}\n    select {d = 10}\n    \"###,\n    )\n    .unwrap(), @r\"\n    WITH table_0 AS (\n      SELECT\n        10 AS d,\n        a\n      FROM\n        tb1\n    )\n    SELECT\n      d\n    FROM\n      table_0\n    ORDER BY\n      a\n    \");\n\n    assert_snapshot!(compile(\n        r###\"\n    from tb1\n    take 10\n    filter true\n    take 20\n    filter true\n    select d = 10\n    \"###,\n    )\n    .unwrap(), @r\"\n    WITH table_1 AS (\n      SELECT\n        NULL\n      FROM\n        tb1\n      LIMIT\n        10\n    ), table_0 AS (\n      SELECT\n        NULL\n      FROM\n        table_1\n      WHERE\n        true\n      LIMIT\n        20\n    )\n    SELECT\n      10 AS d\n    FROM\n      table_0\n    WHERE\n      true\n    \");\n}\n\n#[test]\nfn test_conflicting_names_at_split() {\n    // issue #2697\n    assert_snapshot!(compile(\n        r#\"\n    from s = workflow_steps\n    join wp=workflow_phases (s.phase_id == wp.id)\n    filter wp.name == \"CREATE_OUTLET\"\n    join w=workflow (wp.workflow_id == w.id)\n    select {\n        step_id = s.id,\n        phase_id = wp.id,\n    }\n    \"#,\n    )\n    .unwrap(), @r\"\n    WITH table_0 AS (\n      SELECT\n        wp.id,\n        s.id AS _expr_0,\n        wp.workflow_id\n      FROM\n        workflow_steps AS s\n        INNER JOIN workflow_phases AS wp ON s.phase_id = wp.id\n      WHERE\n        wp.name = 'CREATE_OUTLET'\n    )\n    SELECT\n      table_0._expr_0 AS step_id,\n      table_0.id AS phase_id\n    FROM\n      table_0\n      INNER JOIN workflow AS w ON table_0.workflow_id = w.id\n    \");\n}\n\n#[test]\nfn test_relation_literal_quoting() {\n    // issue #3484\n    assert_snapshot!(compile(\n        r###\"\n    from [\n        {`small number`=1e-10, `large number`=1e10},\n    ]\n    select {`small number`, `large number`}\n    \"###,\n    )\n    .unwrap(), @r#\"\n    WITH table_0 AS (\n      SELECT\n        1e-10 AS \"small number\",\n        10000000000.0 AS \"large number\"\n    )\n    SELECT\n      \"small number\",\n      \"large number\"\n    FROM\n      table_0\n    \"#);\n}\n\n#[test]\nfn test_relation_var_name_clashes_01() {\n    assert_snapshot!(compile(\n        r###\"\n    let table_0 = (from a)\n\n    from table_0\n    take 10\n    filter x > 0\n        \"###,\n    )\n    .unwrap(), @r\"\n    WITH table_0 AS (\n      SELECT\n        *\n      FROM\n        a\n    ),\n    table_1 AS (\n      SELECT\n        *\n      FROM\n        table_0\n      LIMIT\n        10\n    )\n    SELECT\n      *\n    FROM\n      table_1\n    WHERE\n      x > 0\n    \");\n}\n\n#[test]\nfn test_relation_var_name_clashes_02() {\n    // issue #3713\n    assert_snapshot!(compile(\n        r###\"\n    from t\n    join t (==x)\n        \"###,\n    )\n    .unwrap(), @r\"\n    SELECT\n      t.*,\n      table_0.*\n    FROM\n      t\n      INNER JOIN t AS table_0 ON t.x = table_0.x\n    \");\n}\n\n#[test]\n#[ignore]\nfn test_select_this() {\n    // Currently broken for a few reasons:\n    // - type of `this` is not resolved as tuple, but an union?\n    // - lineage is not computed correctly\n    assert_snapshot!(compile(\n        r###\"\n    from x\n    select {a, b}\n    select this\n        \"###,\n    )\n    .unwrap(), @r###\"\n    SELECT\n      a,\n      b\n    FROM\n      x\n    \"###);\n}\n\n#[test]\nfn test_select_bare_wildcard() {\n    // Regression test for #5694: bare `*` in `select` should produce\n    // a helpful error, not panic.\n    assert_snapshot!(compile(r#\"\n    from page_titles\n    select *\n    \"#).unwrap_err(),\n        @\"\n    Error:\n       ╭─[ :3:12 ]\n       │\n     3 │     select *\n       │            ┬\n       │            ╰── Column wildcard `*` must be qualified, e.g. `table_name.*`\n    ───╯\n    \"\n    );\n}\n\n#[test]\nfn test_select_repeated_and_derived() {\n    assert_snapshot!(compile(\n        r###\"\n    from tb_0\n    take  100\n    select {cc0 = c1,cc1 = c2,cc2 = c1}\n    select {ccc0 = cc1,ccc1 = 1}\n    select {cccc0 = 1,cccc1 = ccc0,cccc3 = 1,cccc4 = 0,cccc5 = 0,cccc6 = 0,cccc7 = 0}\n    derive {cccc8 = 0,cccc9 = 0,cccc10 = 0}\n        \"###,\n    )\n    .unwrap(), @r###\"\n    WITH table_0 AS (\n      SELECT\n        c2 AS _expr_0\n      FROM\n        tb_0\n      LIMIT\n        100\n    )\n    SELECT\n      1 AS cccc0,\n      _expr_0 AS cccc1,\n      1 AS cccc3,\n      0 AS cccc4,\n      0 AS cccc5,\n      0 AS cccc6,\n      0 AS cccc7,\n      0 AS cccc8,\n      0 AS cccc9,\n      0 AS cccc10\n    FROM\n      table_0\n    \"###);\n}\n\n#[test]\nfn test_group_exclude() {\n    assert_snapshot!(compile(\n        r###\"\n    from x\n    select {a, b}\n    group {a} (derive c = a + 1)\n        \"###,\n    )\n    .unwrap_err(), @r\"\n    Error:\n       ╭─[ :4:27 ]\n       │\n     4 │     group {a} (derive c = a + 1)\n       │                           ┬\n       │                           ╰── Unknown name `a`\n       │\n       │ Help: available columns: x.b\n    ───╯\n    \");\n\n    // assert_snapshot!(compile(\n    //     r###\"\n    // from x\n    // select {a, b}\n    // group {a + 1} (aggregate {sum b})\n    //     \"###,\n    // )\n    // .unwrap_err(), @r###\"\n    // SELECT\n    //   a,\n    //   b\n    // FROM\n    //   x\n    // \"###);\n}\n\n#[test]\nfn test_table_declarations() {\n    assert_snapshot!(compile(\n        r###\"\n    module default_db {\n      module my_schema {\n        let my_table <[{ id = int, a = text }]>\n      }\n\n      let another_table <[{ id = int, b = text }]>\n    }\n\n    from my_schema.my_table | join another_table (==id) | take 10\n        \"###,\n    )\n    .unwrap(), @r\"\n    SELECT\n      my_table.id,\n      my_table.a,\n      another_table.id,\n      another_table.b\n    FROM\n      my_schema.my_table\n      INNER JOIN another_table ON my_table.id = another_table.id\n    LIMIT\n      10\n    \");\n}\n\n#[test]\nfn test_param_declarations() {\n    assert_snapshot!(compile(\n        r###\"\n    let a <int>\n\n    from x | filter b == a\n        \"###,\n    )\n    .unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      x\n    WHERE\n      b = $a\n    \");\n}\n\n#[test]\nfn test_relation_aliasing() {\n    assert_snapshot!(compile(\n        r###\"\n    from x | select {y = this} | select {y.hello}\n        \"###,\n    )\n    .unwrap(), @r\"\n    SELECT\n      hello\n    FROM\n      x\n    \");\n}\n\n#[test]\nfn test_import() {\n    assert_snapshot!(compile(\n        r###\"\n    module hello {\n        let world = 1\n    }\n\n    import a = hello.world\n\n    from x | select a\n        \"###,\n    )\n    .unwrap(), @r\"\n    SELECT\n      1\n    FROM\n      x\n    \");\n}\n\n#[test]\nfn unstable_ordering() {\n    // https://github.com/PRQL/prql/issues/5053\n    assert_snapshot!(compile(r###\"\n  # All lines are mandatory\nfrom foo\ntake 10000\n# We need 8+ aliases to trigger the issue\nderive { a1 = 1, a2 = 1, a3 = 1, a4 = 1, a5 = 1, a6 = 1, a7 = 1, a8 = 1 }\n# The `select !` itself is required, but its content is not\nselect !{ a1, a2, a3, a4, a5, a6, a7, a8 }\n\n# We may remove `u` from both these statements, but the `select !` must remain\nselect { b, c, u }\nselect !{ u }\n\n# Aggregate verb seems to not matter\ngroup { b } ( aggregate { c = count c } )\nderive { d = c }\nselect !{ c }\n\ngroup { d } ( aggregate { b = sum b } )\nsort { d }\"###).unwrap(), @r\"\n    WITH table_1 AS (\n      SELECT\n        b\n      FROM\n        foo\n      LIMIT\n        10000\n    ), table_0 AS (\n      SELECT\n        b,\n        COUNT(*) AS _expr_0\n      FROM\n        table_1\n      GROUP BY\n        b\n    )\n    SELECT\n      _expr_0 AS d,\n      COALESCE(SUM(b), 0) AS b\n    FROM\n      table_0\n    GROUP BY\n      _expr_0\n    ORDER BY\n      d\n    \");\n}\n\n#[test]\nfn test_type_error_placement() {\n    assert_snapshot!(compile(r###\"\n    let foo = x -> (x | as integer)\n    from t\n    select (true && (foo y))\n    \"###).unwrap(), @r\"\n    SELECT\n      true\n      AND CAST(y AS integer)\n    FROM\n      t\n    \");\n}\n\n#[test]\nfn test_missing_columns_group_complex_compute() {\n    // https://github.com/PRQL/prql/issues/5354\n    // The focus for this tests is on whether the `hire_date` column is available where it's needed.\n    // Additional `city` derive are there only to trigger the issue.\n    assert_snapshot!(compile(\n        r#\"prql target:sql.postgres\n        from employees\n        derive `year` = s'EXTRACT(year from {`hire_date`})'\n        derive { `year_label` = f\"Year {`year`}\" }\n        derive { `city` = case [ this.`city` == \"Calgary\" => \"A city\", true => this.`city` ] }\n        derive { `city` = case [ this.`city` == \"Edmonton\" => \"Another city\", true => this.`city` ] }\n        group {`year`, `year_label`} (take 1)\n        select {this.`year_label`}\n    \"#,\n    )\n    .unwrap(), @r\"\n    SELECT\n      DISTINCT ON (\n        EXTRACT(\n          year\n          from\n            hire_date\n        ),\n        CONCAT(\n          'Year ',\n          EXTRACT(\n            year\n            from\n              hire_date\n          )\n        )\n      ) CONCAT(\n        'Year ',\n        EXTRACT(\n          year\n          from\n            hire_date\n        )\n      ) AS year_label\n    FROM\n      employees\n    \");\n}\n\n#[test]\nfn test_append_select_compute() {\n    // Test for handling complex append with select and compute operations\n    assert_snapshot!(compile(r###\"\n    from invoices\n    derive total = case [total < 10 => total * 2, true => total]\n    select { customer_id, invoice_id, total }\n    take 5\n    append (\n      from invoice_items\n      derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\n      select { invoice_line_id, invoice_id, unit_price }\n      take 5\n    )\n    select { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\n    \"###).unwrap(), @r\"\n    WITH table_1 AS (\n      SELECT\n        *\n      FROM\n        (\n          SELECT\n            invoice_id,\n            CASE\n              WHEN total < 10 THEN total * 2\n              ELSE total\n            END AS _expr_0,\n            customer_id\n          FROM\n            invoices\n          LIMIT\n            5\n        ) AS table_3\n      UNION\n      ALL\n      SELECT\n        *\n      FROM\n        (\n          SELECT\n            invoice_id,\n            CASE\n              WHEN unit_price < 1 THEN unit_price * 2\n              ELSE unit_price\n            END AS unit_price,\n            invoice_line_id\n          FROM\n            invoice_items\n          LIMIT\n            5\n        ) AS table_4\n    )\n    SELECT\n      customer_id * 2 AS a,\n      ROUND(invoice_id * _expr_0, 1) AS b\n    FROM\n      table_1\n    \");\n}\n\n#[test]\nfn test_append_select_multiple() {\n    // Test for handling multiple append operations with grouping and aggregation\n    assert_snapshot!(compile(r###\"\n    from invoices\n    select { customer_id, invoice_id, total, useless1, useless2 }\n    take 5\n    append (\n      from employees\n      select { employee_id, employee_id + 1, reports_to, useless3, useless4 }\n      take 5\n    )\n    group { customer_id } (aggregate { invoice_id = math.round 1 (sum invoice_id), total = math.round 1 (sum total), useless1 = sum useless1 })\n    append (\n      from invoice_items\n      select { invoice_id, invoice_line_id, 0, useless5 }\n      take 5\n    )\n    sort { +invoice_id, +total }\n    select { total, invoice_id }\n    \"###).unwrap(), @\"\n    WITH table_3 AS (\n      SELECT\n        *\n      FROM\n        (\n          SELECT\n            customer_id,\n            total,\n            invoice_id\n          FROM\n            invoices\n          LIMIT\n            5\n        ) AS table_6\n      UNION\n      ALL\n      SELECT\n        *\n      FROM\n        (\n          SELECT\n            employee_id,\n            reports_to,\n            employee_id + 1\n          FROM\n            employees\n          LIMIT\n            5\n        ) AS table_7\n    ),\n    table_2 AS (\n      SELECT\n        ROUND(COALESCE(SUM(total), 0), 1) AS total,\n        ROUND(COALESCE(SUM(invoice_id), 0), 1) AS invoice_id\n      FROM\n        table_3\n      GROUP BY\n        customer_id\n      UNION\n      ALL\n      SELECT\n        *\n      FROM\n        (\n          SELECT\n            invoice_id,\n            invoice_line_id\n          FROM\n            invoice_items\n          LIMIT\n            5\n        ) AS table_8\n    )\n    SELECT\n      total,\n      invoice_id\n    FROM\n      table_2\n    ORDER BY\n      invoice_id,\n      total\n    \");\n}\n\n#[test]\nfn test_append_with_cte() {\n    // Regression test for issue #5494 - append with CTEs (let statements)\n    // Tests that positional mapping doesn't cause out-of-bounds errors\n    assert_snapshot!(compile(r###\"\n    prql target:sql.postgres\n\n    let invoices_wrap = (\n      from invoices\n      select { invoice_id, billing_country }\n    )\n\n    let employees_wrap = (\n      from employees\n      select { employee_id, country }\n    )\n\n    from invoices_wrap\n    derive { source = \"invoices\" }\n    append (\n      from employees_wrap\n      derive { source = \"employees\" }\n    )\n    \"###).unwrap(), @r\"\n    WITH invoices_wrap AS (\n      SELECT\n        invoice_id,\n        billing_country\n      FROM\n        invoices\n    ),\n    employees_wrap AS (\n      SELECT\n        employee_id,\n        country\n      FROM\n        employees\n    )\n    SELECT\n      invoice_id,\n      billing_country,\n      'invoices' AS source\n    FROM\n      invoices_wrap\n    UNION\n    ALL\n    SELECT\n      employee_id,\n      country,\n      'employees' AS source\n    FROM\n      employees_wrap\n    \");\n}\n\n#[test]\nfn test_distinct_on_sort_on_compute() {\n    // Test for handling distinct on with sorting on computed columns.\n    // Note: table_0 includes billing_city and _expr_1 even though they're unused\n    // downstream. This is a side effect of the fix for #5130 which keeps Computes\n    // together with Aggregates when Filter follows.\n    assert_snapshot!(compile(r###\"\n    from invoices\n    derive code = case [customer_id < 10 => billing_postal_code, true => null]\n    group {customer_id, billing_city, billing_country} (\n      sort {-this.code}\n      take 1\n    )\n    filter (customer_id | in [4])\n    group {billing_country} (aggregate {total = math.round 2 (sum total)})\n    \"###).unwrap(), @\"\n    WITH table_1 AS (\n      SELECT\n        billing_country,\n        total,\n        customer_id,\n        billing_city,\n        CASE\n          WHEN customer_id < 10 THEN billing_postal_code\n          ELSE NULL\n        END AS _expr_1,\n        billing_postal_code\n      FROM\n        invoices\n    ),\n    table_0 AS (\n      SELECT\n        billing_country,\n        total,\n        customer_id,\n        ROW_NUMBER() OVER (\n          PARTITION BY customer_id,\n          billing_city,\n          billing_country\n          ORDER BY\n            _expr_1 DESC\n        ) AS _expr_0,\n        billing_city,\n        _expr_1\n      FROM\n        table_1\n    )\n    SELECT\n      billing_country,\n      ROUND(COALESCE(SUM(total), 0), 2) AS total\n    FROM\n      table_0\n    WHERE\n      _expr_0 <= 1\n      AND customer_id IN (4)\n    GROUP BY\n      billing_country\n    \");\n}\n\n/// Ensures that the sort happens on `table0`.`_expr_0` and not on `table2`.`_expr_0`\n#[test]\nfn test_sort_cast_filter_join_select() {\n    assert_snapshot!(compile(r###\"\n    from albums\n    select { this.`title`, this.`artist_id`, this.`album_id` }\n    sort { this.`artist_id`, this.`album_id` }\n    derive { `artist_id` = as `double precision` this.`artist_id` }\n    filter (this.`artist_id` != null)\n    join side:left artists (this.`artist_id` == that.`artist_id`)\n    select {this.`artist_id`, this.`title`, this.`name`}\n    \"###\n    ).unwrap(), @\"\n    WITH table_1 AS (\n      SELECT\n        CAST(artist_id AS double precision) AS artist_id,\n        title,\n        artist_id AS _expr_0,\n        album_id\n      FROM\n        albums\n    ),\n    table_2 AS (\n      SELECT\n        artist_id,\n        title,\n        _expr_0,\n        album_id\n      FROM\n        table_1\n      WHERE\n        artist_id IS NOT NULL\n    ),\n    table_0 AS (\n      SELECT\n        artist_id,\n        title,\n        _expr_0,\n        album_id\n      FROM\n        table_2\n    )\n    SELECT\n      table_0.artist_id,\n      table_0.title,\n      artists.name\n    FROM\n      table_0\n      LEFT OUTER JOIN artists ON table_0.artist_id = artists.artist_id\n    ORDER BY\n      table_0._expr_0,\n      table_0.album_id\n    \")\n}\n\n/// Ensures that the sort happens on `table1`.`artist_id` and not on `table2`.`artist_id`\n#[test]\nfn test_sort_filter_derive_join_select() {\n    assert_snapshot!(compile(r###\"\n    from albums\n    sort this.`artist_id`\n    filter (this.`artist_id` != null)\n    derive { `artist_id` = as `double precision` this.`artist_id` }\n    join side:left (from artists\n        select {`artist_id_right` = this.`artist_id`}\n    ) (this.`artist_id` == that.`artist_id_right`)\n    select {this.`artist_id`, this.`title`}\n    \"###\n    ).unwrap(), @\"\n    WITH table_2 AS (\n      SELECT\n        CAST(artist_id AS double precision) AS artist_id,\n        title,\n        artist_id AS _expr_0\n      FROM\n        albums\n      WHERE\n        artist_id IS NOT NULL\n    ),\n    table_1 AS (\n      SELECT\n        artist_id,\n        title,\n        _expr_0\n      FROM\n        table_2\n    ),\n    table_0 AS (\n      SELECT\n        artist_id AS artist_id_right\n      FROM\n        artists\n    )\n    SELECT\n      table_1.artist_id,\n      table_1.title\n    FROM\n      table_1\n      LEFT OUTER JOIN table_0 ON table_1.artist_id = table_0.artist_id_right\n    ORDER BY\n      table_1._expr_0\n    \")\n}\n\n/// Ensures that the sort happens on `table0`.`artist_id` and not on `table0`.`double_artist_id`\n#[test]\nfn test_sort_cast_filter_join_select_with_alias() {\n    assert_snapshot!(compile(r###\"\n    from albums\n    select { this.`title`, this.`artist_id`, this.`album_id` }\n    sort { this.`artist_id`, this.`album_id` }\n    derive { `double_artist_id` = as `double precision` this.`artist_id` }\n    filter (this.`artist_id` != null)\n    join side:left artists (this.`double_artist_id` == that.`artist_id`)\n    select {this.`double_artist_id`, this.`title`, this.`name`}\n    \"###\n    ).unwrap(), @\"\n    WITH table_1 AS (\n      SELECT\n        CAST(artist_id AS double precision) AS double_artist_id,\n        title,\n        artist_id,\n        album_id\n      FROM\n        albums\n    ),\n    table_2 AS (\n      SELECT\n        double_artist_id,\n        title,\n        artist_id,\n        album_id\n      FROM\n        table_1\n      WHERE\n        artist_id IS NOT NULL\n    ),\n    table_0 AS (\n      SELECT\n        double_artist_id,\n        title,\n        artist_id,\n        album_id\n      FROM\n        table_2\n    )\n    SELECT\n      table_0.double_artist_id,\n      table_0.title,\n      artists.name\n    FROM\n      table_0\n      LEFT OUTER JOIN artists ON table_0.double_artist_id = artists.artist_id\n    ORDER BY\n      table_0.artist_id,\n      table_0.album_id\n    \")\n}\n\n#[rstest]\n#[case::redshift_quotes_only_keywords(\n    sql::Dialect::Redshift,\n    r#\"\nSELECT\n  invoice_id,\n  \"time\",\n  \"timestamp\",\n  \"identity\",\n  \"system\"\nFROM\n  invoice\n\"#\n)]\n#[case::postgres_does_not_quote_redshift_keywords(\n    sql::Dialect::Postgres,\n    r#\"\nSELECT\n  invoice_id,\n  time,\n  timestamp,\n  identity,\n  system\nFROM\n  invoice\n\"#\n)]\n#[case::generic_does_not_quote_redshift_keywords(\n    sql::Dialect::Generic,\n    r#\"\nSELECT\n  invoice_id,\n  time,\n  timestamp,\n  identity,\n  system\nFROM\n  invoice\n\"#\n)]\nfn test_redshift_keyword_quoting(\n    #[case] dialect: sql::Dialect,\n    #[case] expected_sql: &'static str,\n) {\n    let query = r#\"\n    from invoice\n    select {invoice_id, invoice.time, invoice.timestamp, invoice.identity, invoice.system}\n    \"#;\n\n    assert_eq!(\n        compile_with_sql_dialect(query, dialect).unwrap(),\n        expected_sql.trim_start()\n    )\n}\n\n#[test]\nfn test_redshift_quotes_only_keywords_mixed() {\n    // Test that Redshift quotes ONLY keywords, not all identifiers\n    let query = r#\"\n    from invoice\n    select {\n        invoice_id,\n        invoice.time,\n        invoice.timestamp,\n        customer_name,\n        invoice.identity,\n        amount\n    }\n    \"#;\n\n    let expected = r#\"\nSELECT\n  invoice_id,\n  \"time\",\n  \"timestamp\",\n  customer_name,\n  \"identity\",\n  amount\nFROM\n  invoice\n\"#;\n\n    assert_eq!(\n        compile_with_sql_dialect(query, sql::Dialect::Redshift).unwrap(),\n        expected.trim_start()\n    )\n}\n\n// Test that Redshift uses double pipe (||) over CONCAT\n#[test]\nfn test_redshift_uses_double_pipe_over_concat() {\n    assert_snapshot!(compile_with_sql_dialect(r###\"\n    from invoice\n    derive {\n        concatenated = f\"{col_one} + {col_two}\"\n    }\n    \"###, sql::Dialect::Redshift\n    ).unwrap(), @r\"\n    SELECT\n      *,\n      col_one || ' + ' || col_two AS concatenated\n    FROM\n      invoice\n    \");\n}\n\n#[rstest]\n#[case(\n    \"from t | select { inter = 2weeks }\",\n    \"SELECT\\n  INTERVAL '2 WEEK' AS inter\\nFROM\\n  t\\n\"\n)]\n#[case(\n    \"from t | select { inter = 2months }\",\n    \"SELECT\\n  INTERVAL '2' MONTH AS inter\\nFROM\\n  t\\n\"\n)]\n#[case(\n    \"from t | select { inter = 2hours }\",\n    \"SELECT\\n  INTERVAL '2' HOUR AS inter\\nFROM\\n  t\\n\"\n)]\nfn test_redshift_interval_quoting(#[case] query: &str, #[case] expected: &str) {\n    assert_eq!(\n        compile_with_sql_dialect(query, sql::Dialect::Redshift).unwrap(),\n        expected\n    )\n}\n\n#[test]\nfn test_redshift_text_contains_uses_double_pipe() {\n    assert_snapshot!(compile_with_sql_dialect(r###\"\n    from employees\n    select {\n        name,\n        has_substring = (name | text.contains \"pika\")\n    }\n    \"###, sql::Dialect::Redshift\n    ).unwrap(), @r\"\n    SELECT\n      name,\n      name LIKE '%' || 'pika' || '%' AS has_substring\n    FROM\n      employees\n    \");\n}\n\n#[test]\nfn test_snowflake_row_number_requires_order_by() {\n    // https://github.com/PRQL/prql/issues/5580\n    // Snowflake requires ORDER BY for ROW_NUMBER() in window specification\n    assert_snapshot!(compile_with_sql_dialect(r###\"\n    from invoices\n    group { customer_id } (take 1)\n    \"###, sql::Dialect::Snowflake\n    ).unwrap(), @r#\"\n    WITH \"table_0\" AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (\n          PARTITION BY \"customer_id\"\n          ORDER BY\n            1\n        ) AS \"_expr_0\"\n      FROM\n        \"invoices\"\n    )\n    SELECT\n      * EXCLUDE (\"_expr_0\")\n    FROM\n      \"table_0\"\n    WHERE\n      \"_expr_0\" <= 1\n    \"#);\n}\n\n#[test]\nfn test_snowflake_row_number_with_explicit_sort() {\n    // When user provides explicit sort, it should be used instead of the fallback\n    assert_snapshot!(compile_with_sql_dialect(r###\"\n    from invoices\n    group { customer_id } (sort invoice_date | take 1)\n    \"###, sql::Dialect::Snowflake\n    ).unwrap(), @r#\"\n    WITH \"table_0\" AS (\n      SELECT\n        *,\n        ROW_NUMBER() OVER (\n          PARTITION BY \"customer_id\"\n          ORDER BY\n            \"invoice_date\"\n        ) AS \"_expr_0\"\n      FROM\n        \"invoices\"\n    )\n    SELECT\n      * EXCLUDE (\"_expr_0\")\n    FROM\n      \"table_0\"\n    WHERE\n      \"_expr_0\" <= 1\n    \"#);\n}\n\n#[test]\nfn test_snowflake_text_length_uses_length() {\n    // Snowflake should use LENGTH instead of CHAR_LENGTH\n    assert_snapshot!(compile_with_sql_dialect(r###\"\n    from employees\n    select {\n        name_length = (name | text.length)\n    }\n    \"###, sql::Dialect::Snowflake\n    ).unwrap(), @r#\"\n    SELECT\n      LENGTH(\"name\") AS \"name_length\"\n    FROM\n      \"employees\"\n    \"#);\n}\n\n#[test]\nfn test_group_with_only_sort() {\n    // Issue #5092: group with only sort (no take) should not cause ICE.\n    // The sort inside a group without take is semantically a no-op,\n    // so it should compile to just the table.\n    assert_snapshot!(compile(r###\"\n    from a = employees\n    group { a.department } (\n        sort a.salary\n    )\n    \"###).unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      employees AS a\n    \");\n}\n\n#[test]\nfn test_group_empty_preserves_sort() {\n    // Issue #5100: Empty group {} should preserve inner sort.\n    assert_snapshot!(compile(r###\"\n    from foo\n    group {} (\n        sort a\n        take 1\n    )\n    \"###).unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      foo\n    ORDER BY\n      a\n    LIMIT\n      1\n    \");\n}\n\n#[test]\nfn test_source_column_name() {\n    // Issue #5094: Using `source` as a column name should not cause\n    // \"Ambiguous name\" error.\n    assert_snapshot!(compile(r###\"\n    let table1 = (\n        from ScrapedData\n        select { sd=SD_Land_Use_Code }\n        derive source=\"SD\"\n        group { sd } (take 1)\n        sort { sd }\n    )\n\n    let table2 = (\n        from SpecialLand\n        select { sd = SL_Code }\n        derive source = \"SL\"\n        group { sd } (take 1)\n        sort { sd }\n    )\n\n    from table1\n    append table2\n    \"###).unwrap(), @r#\"\n    WITH table_0 AS (\n      SELECT\n        \"SD_Land_Use_Code\" AS sd,\n        'SD' AS source,\n        ROW_NUMBER() OVER (PARTITION BY \"SD_Land_Use_Code\") AS _expr_0\n      FROM\n        \"ScrapedData\"\n    ),\n    table1 AS (\n      SELECT\n        sd,\n        source\n      FROM\n        table_0\n      WHERE\n        _expr_0 <= 1\n    ),\n    table_1 AS (\n      SELECT\n        \"SL_Code\" AS sd,\n        'SL' AS source,\n        ROW_NUMBER() OVER (PARTITION BY \"SL_Code\") AS _expr_1\n      FROM\n        \"SpecialLand\"\n    )\n    SELECT\n      sd,\n      source\n    FROM\n      table1\n    UNION\n    ALL\n    SELECT\n      *\n    FROM\n      (\n        SELECT\n          sd,\n          source\n        FROM\n          table_1\n        WHERE\n          _expr_1 <= 1\n        ORDER BY\n          sd\n      ) AS table_2\n    \"#);\n}\n\n#[test]\nfn test_column_inference_with_into() {\n    // Issue #4723: Column inference should work correctly with `into`.\n    assert_snapshot!(compile(r###\"\n    from data\n    join side:left other (other.id == data.id)\n    into A\n\n    from A\n    select {\n        A.val\n    }\n    \"###).unwrap(), @r#\"\n    WITH \"A\" AS (\n      SELECT\n        data.*,\n        other.*\n      FROM\n        data\n        LEFT OUTER JOIN other ON other.id = data.id\n    )\n    SELECT\n      val\n    FROM\n      \"A\"\n    \"#);\n}\n\n#[test]\nfn test_distinct_on_columns_propagated() {\n    // Issue #4432: DISTINCT ON should propagate all necessary columns to the CTE.\n    assert_snapshot!(compile(r###\"\n    prql target:sql.postgres\n\n    from src\n    group {grouped_field} (\n        sort {sort_1}\n        take 2..3\n        sort {sort_2}\n        take 1\n    )\n    select { foo }\n    \"###).unwrap(), @\"\n    WITH table_0 AS (\n      SELECT\n        foo,\n        grouped_field,\n        sort_2,\n        ROW_NUMBER() OVER (\n          PARTITION BY grouped_field\n          ORDER BY\n            sort_1\n        ) AS _expr_0\n      FROM\n        src\n    )\n    SELECT\n      DISTINCT ON (grouped_field) foo\n    FROM\n      table_0\n    WHERE\n      _expr_0 BETWEEN 2 AND 3\n    ORDER BY\n      grouped_field,\n      sort_2\n    \");\n}\n\n#[test]\nfn test_sort_take_before_aggregate() {\n    // Issue #5401: sort|take before an aggregation should enforce the sort in the CTE.\n    // Previously, the ORDER BY was lost because the sort was embedded in the Take struct\n    // but not emitted before the LIMIT.\n    assert_snapshot!(compile(r###\"\n    from my_table\n    sort {-this.Total}\n    take 7\n    group { this.network } ( aggregate { total_sum = sum this.Total } )\n    sort {-this.total_sum}\n    \"###).unwrap(), @r#\"\n    WITH table_0 AS (\n      SELECT\n        network,\n        \"Total\"\n      FROM\n        my_table\n      ORDER BY\n        \"Total\" DESC\n      LIMIT\n        7\n    )\n    SELECT\n      network,\n      COALESCE(SUM(\"Total\"), 0) AS total_sum\n    FROM\n      table_0\n    GROUP BY\n      network\n    ORDER BY\n      total_sum DESC\n    \"#);\n}\n\n#[test]\nfn test_aggregate_with_operations_and_filter() {\n    // Issue #5130: Filtering on combined aggregates was generating invalid SQL.\n    // The CTE was missing GROUP BY when aggregate expressions had operations.\n    // Previously, this produced a CTE with SUM() but no GROUP BY clause.\n    assert_snapshot!(compile(r###\"\n    from invoices\n    group billing_city (\n      aggregate{\n        sum_c = (sum customer_id) + (sum customer_id)\n      }\n    )\n    filter sum_c > 0\n    \"###).unwrap(), @\"\n    SELECT\n      billing_city,\n      COALESCE(SUM(customer_id), 0) + COALESCE(SUM(customer_id), 0) AS sum_c\n    FROM\n      invoices\n    GROUP BY\n      billing_city\n    HAVING\n      COALESCE(SUM(customer_id), 0) + COALESCE(SUM(customer_id), 0) > 0\n    \");\n\n    // Also test with scalar operations\n    assert_snapshot!(compile(r###\"\n    from invoices\n    group billing_city (\n      aggregate{\n        sum_c = (sum customer_id) * 2\n      }\n    )\n    filter sum_c > 0\n    \"###).unwrap(), @\"\n    SELECT\n      billing_city,\n      COALESCE(SUM(customer_id), 0) * 2 AS sum_c\n    FROM\n      invoices\n    GROUP BY\n      billing_city\n    HAVING\n      COALESCE(SUM(customer_id), 0) * 2 > 0\n    \");\n}\n\n/// Regression test for issue #5661: partial application of transforms in\n/// user-defined functions.\n///\n/// When a user defines a function that wraps a transform with fewer parameters\n/// than the transform requires (e.g., `let foo = a -> take a`), the missing\n/// parameters should be propagated to the wrapper function, allowing it to work\n/// correctly in pipelines.\n#[test]\nfn test_partial_application_of_transform() {\n    // Basic case: wrapping `take` with a single parameter\n    assert_snapshot!(compile(r#\"\n    let foo = a -> take a\n    from invoices | foo 10\n    \"#).unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      invoices\n    LIMIT\n      10\n    \");\n\n    // Same behavior as explicit two-parameter version\n    assert_snapshot!(compile(r#\"\n    let foo = a r -> take a r\n    from invoices | foo 10\n    \"#).unwrap(), @r\"\n    SELECT\n      *\n    FROM\n      invoices\n    LIMIT\n      10\n    \");\n}\n"
  },
  {
    "path": "prqlc/prqlc-macros/Cargo.toml",
    "content": "[package]\ndescription = \"Macros for PRQL compilation at build time\"\nname = \"prqlc-macros\"\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nversion.workspace = true\n\n[lib]\nbench = false\ndoctest = false\nproc-macro = true\ntest = false\n\n[dependencies]\nprqlc = {path = \"../prqlc\", default-features = false, version = \"0.13.12\" }\nsyn = \"2.0.117\"\n\n[package.metadata.release]\ntag-name = \"{{version}}\"\ntag-prefix = \"\"\n"
  },
  {
    "path": "prqlc/prqlc-macros/src/lib.rs",
    "content": "//! Macros for PRQL compilation at build time.\n//!\n//! ```\n//! use prqlc_macros::prql_to_sql;\n//!\n//! let sql: &str = prql_to_sql!(\"from albums | select {title, artist_id}\");\n//! assert_eq!(sql, \"SELECT title, artist_id FROM albums\");\n//! ```\n//!\n//! \"at build time\" means that PRQL will be compiled during Rust compilation,\n//! producing errors alongside Rust errors. Limited to string literals.\nuse proc_macro::{Literal, TokenStream, TokenTree};\nuse syn::{Expr, ExprLit, Lit};\n\n#[proc_macro]\npub fn prql_to_sql(input: TokenStream) -> TokenStream {\n    let input: Expr = syn::parse(input).unwrap();\n\n    let prql_string = match input {\n        Expr::Lit(ExprLit {\n            lit: Lit::Str(lit_str),\n            ..\n        }) => lit_str.value(),\n        _ => panic!(\"prql! proc macro expected a string\"),\n    };\n\n    let opts = prqlc::Options::default().no_format().no_signature();\n\n    let sql_string = match prqlc::compile(&prql_string, &opts) {\n        Ok(r) => r,\n        Err(err) => {\n            panic!(\"{}\", err);\n        }\n    };\n\n    TokenStream::from_iter(vec![TokenTree::Literal(Literal::string(&sql_string))])\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/Cargo.toml",
    "content": "[package]\ndescription = \"A parser for the PRQL query language.\"\nname = \"prqlc-parser\"\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nversion.workspace = true\n\n[lib]\nbench = false\ndoctest = false\n\n[dependencies]\nchumsky = { version = \"0.12.0\", default-features = false, features = [\n    \"std\",\n    \"pratt\",\n] }\nenum-as-inner = { workspace = true }\nitertools = { workspace = true }\nlog = { workspace = true }\nschemars = { workspace = true }\nsemver = { workspace = true }\nserde = { workspace = true }\nserde_yaml = { workspace = true, optional = true }\nstrum = { workspace = true }\n\n[dev-dependencies]\ninsta = { workspace = true }\nserde_json = { workspace = true }\n\n[lints.rust]\n# https://github.com/taiki-e/cargo-llvm-cov/blob/4039500dc7ce5874748769166f1f481be294c90f/README.md#exclude-function-from-coverage\nunexpected_cfgs = { level = \"warn\", check-cfg = [\n    'cfg(coverage,coverage_nightly)',\n] }\nunsafe_code = \"forbid\"\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/error.rs",
    "content": "use std::fmt::Debug;\n\nuse serde::Serialize;\n\nuse crate::span::Span;\n\n/// A prqlc error. Used internally, exposed as prqlc::ErrorMessage.\n#[derive(Debug, Clone)]\npub struct Error {\n    /// Message kind. Currently only Error is implemented.\n    pub kind: MessageKind,\n    pub span: Option<Span>,\n    pub reason: Reason,\n    pub hints: Vec<String>,\n    /// Machine readable identifier error code eg, \"E0001\"\n    pub code: Option<&'static str>,\n    // pub source: ErrorSource\n}\n\n#[derive(Clone, Debug, Default)]\npub enum ErrorSource {\n    Lexer(String),\n    #[default]\n    Unknown,\n    NameResolver,\n    TypeResolver,\n    SQL,\n    Internal {\n        message: String,\n    },\n}\n\n/// Multiple prqlc errors. Used internally, exposed as prqlc::ErrorMessages.\n#[derive(Debug, Clone)]\npub struct Errors(pub Vec<Error>);\n\n/// Compile message kind. Currently only Error is implemented.\n#[derive(Clone, Debug, PartialEq, Eq, Serialize)]\npub enum MessageKind {\n    Error,\n    Warning,\n    Lint,\n}\n\n#[derive(Debug, Clone)]\npub enum Reason {\n    Simple(String),\n    Expected {\n        /// Where we were\n        // (could rename to `where` / `location` / `within`?)\n        who: Option<String>,\n        /// What we expected\n        expected: String,\n        /// What we found\n        found: String,\n    },\n    Unexpected {\n        found: String,\n    },\n    NotFound {\n        name: String,\n        namespace: String,\n    },\n    Bug {\n        issue: Option<i32>,\n        details: Option<String>,\n    },\n    Internal {\n        message: String,\n    },\n}\n\nimpl Error {\n    pub fn new(reason: Reason) -> Self {\n        Error {\n            kind: MessageKind::Error,\n            span: None,\n            reason,\n            hints: Vec::new(),\n            code: None,\n            // source: ErrorSource::default()\n        }\n    }\n\n    pub fn new_simple<S: ToString>(reason: S) -> Self {\n        Error::new(Reason::Simple(reason.to_string()))\n    }\n\n    pub fn new_bug(issue_no: i32) -> Self {\n        Error::new(Reason::Bug {\n            issue: Some(issue_no),\n            details: None,\n        })\n    }\n\n    /// Used for things that you *think* should never happen, but are not sure.\n    pub fn new_assert<S: ToString>(details: S) -> Self {\n        Error::new(Reason::Bug {\n            issue: None,\n            details: Some(details.to_string()),\n        })\n    }\n}\n\nimpl std::fmt::Display for Reason {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Reason::Simple(text) => f.write_str(text),\n            Reason::Expected {\n                who,\n                expected,\n                found,\n            } => {\n                if let Some(who) = who {\n                    write!(f, \"{who} \")?;\n                }\n                write!(f, \"expected {expected}, but found {found}\")\n            }\n            Reason::Unexpected { found } => write!(f, \"unexpected {found}\"),\n            Reason::NotFound { name, namespace } => write!(f, \"{namespace} `{name}` not found\"),\n            Reason::Bug { issue, details } => {\n                write!(f, \"internal compiler error\")?;\n                if let Some(details) = details {\n                    write!(f, \"; {details}\")?;\n                }\n                if let Some(issue_no) = issue {\n                    write!(\n                        f,\n                        \"; tracked at https://github.com/PRQL/prql/issues/{issue_no}\"\n                    )?;\n                }\n                Ok(())\n            }\n            Reason::Internal { message } => {\n                write!(f, \"internal error: {message}\")\n            }\n        }\n    }\n}\n\nimpl From<Error> for Errors {\n    fn from(error: Error) -> Self {\n        Errors(vec![error])\n    }\n}\n\n// Needed for anyhow\nimpl std::error::Error for Error {}\n\n// Needed for anyhow\nimpl std::error::Error for Errors {}\n\n// Needed for StdError\nimpl std::fmt::Display for Error {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        Debug::fmt(&self, f)\n    }\n}\n\n// Needed for StdError\nimpl std::fmt::Display for Errors {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        Debug::fmt(&self, f)\n    }\n}\n\npub trait WithErrorInfo: Sized {\n    fn push_hint<S: Into<String>>(self, hint: S) -> Self;\n\n    fn with_hints<S: Into<String>, I: IntoIterator<Item = S>>(self, hints: I) -> Self;\n\n    fn with_span(self, span: Option<Span>) -> Self;\n\n    fn with_span_fallback(self, span: Option<Span>) -> Self;\n\n    fn with_code(self, code: &'static str) -> Self;\n\n    fn with_source(self, source: ErrorSource) -> Self;\n}\n\nimpl WithErrorInfo for Error {\n    fn push_hint<S: Into<String>>(mut self, hint: S) -> Self {\n        self.hints.push(hint.into());\n        self\n    }\n\n    fn with_hints<S: Into<String>, I: IntoIterator<Item = S>>(mut self, hints: I) -> Self {\n        self.hints = hints.into_iter().map(|x| x.into()).collect();\n        self\n    }\n\n    fn with_span(mut self, span: Option<Span>) -> Self {\n        self.span = span;\n        self\n    }\n\n    fn with_code(mut self, code: &'static str) -> Self {\n        self.code = Some(code);\n        self\n    }\n\n    fn with_span_fallback(mut self, span: Option<Span>) -> Self {\n        self.span = self.span.or(span);\n        self\n    }\n\n    fn with_source(self, _source: ErrorSource) -> Self {\n        // self.source = source;\n        self\n    }\n}\n\nimpl<T, E: WithErrorInfo> WithErrorInfo for Result<T, E> {\n    fn push_hint<S: Into<String>>(self, hint: S) -> Self {\n        self.map_err(|e| e.push_hint(hint))\n    }\n\n    fn with_hints<S: Into<String>, I: IntoIterator<Item = S>>(self, hints: I) -> Self {\n        self.map_err(|e| e.with_hints(hints))\n    }\n\n    fn with_span(self, span: Option<Span>) -> Self {\n        self.map_err(|e| e.with_span(span))\n    }\n\n    fn with_span_fallback(self, span: Option<Span>) -> Self {\n        self.map_err(|e| e.with_span_fallback(span))\n    }\n\n    fn with_code(self, code: &'static str) -> Self {\n        self.map_err(|e| e.with_code(code))\n    }\n\n    fn with_source(self, source: ErrorSource) -> Self {\n        self.map_err(|e| e.with_source(source))\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/generic.rs",
    "content": "/// Generic definitions of various AST items.\n//\n// This was added in a big refactor by a generous-but-new contributor, and\n// hasn't been used much since, and I'm not sure carries its weight. So we\n// could consider rolling back to only concrete implementations to delayer the\n// code.\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n/// Inclusive-inclusive range.\n/// Missing bound means unbounded range.\n#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]\npub struct Range<T> {\n    pub start: Option<T>,\n    pub end: Option<T>,\n}\n\nimpl<T> Range<T> {\n    pub const fn unbounded() -> Self {\n        Range {\n            start: None,\n            end: None,\n        }\n    }\n\n    pub fn try_map<U, E, F: Fn(T) -> Result<U, E>>(self, f: F) -> Result<Range<U>, E> {\n        Ok(Range {\n            start: self.start.map(&f).transpose()?,\n            end: self.end.map(f).transpose()?,\n        })\n    }\n\n    pub fn map<U, F: Fn(T) -> U>(self, f: F) -> Range<U> {\n        Range {\n            start: self.start.map(&f),\n            end: self.end.map(f),\n        }\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]\npub enum InterpolateItem<T> {\n    String(String),\n    Expr {\n        expr: Box<T>,\n        format: Option<String>,\n    },\n}\n\nimpl<T> InterpolateItem<T> {\n    pub fn map<U, F: Fn(T) -> U>(self, f: F) -> InterpolateItem<U> {\n        match self {\n            Self::String(s) => InterpolateItem::String(s),\n            Self::Expr { expr, format } => InterpolateItem::Expr {\n                expr: Box::new(f(*expr)),\n                format,\n            },\n        }\n    }\n\n    pub fn try_map<U, E, F: Fn(T) -> Result<U, E>>(self, f: F) -> Result<InterpolateItem<U>, E> {\n        Ok(match self {\n            Self::String(s) => InterpolateItem::String(s),\n            Self::Expr { expr, format } => InterpolateItem::Expr {\n                expr: Box::new(f(*expr)?),\n                format,\n            },\n        })\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct SwitchCase<T> {\n    pub condition: T,\n    pub value: T,\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/lexer/lr.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse enum_as_inner::EnumAsInner;\nuse schemars::JsonSchema;\n\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct Tokens(pub Vec<Token>);\n\n#[derive(Clone, PartialEq, Serialize, Deserialize, Eq, JsonSchema)]\npub struct Token {\n    pub kind: TokenKind,\n    pub span: std::ops::Range<usize>,\n}\n\n#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, JsonSchema)]\npub enum TokenKind {\n    NewLine,\n\n    Ident(String),\n    Keyword(String),\n    #[cfg_attr(\n        feature = \"serde_yaml\",\n        serde(with = \"serde_yaml::with::singleton_map\"),\n        schemars(with = \"Literal\")\n    )]\n    Literal(Literal),\n    /// A parameter such as `$1`\n    Param(String),\n\n    Range {\n        /// Whether the left side of the range is bound by the previous token\n        /// (but it's not contained in this token)\n        bind_left: bool,\n        bind_right: bool,\n    },\n    Interpolation(char, String),\n\n    /// single-char control tokens\n    Control(char),\n\n    ArrowThin,   // ->\n    ArrowFat,    // =>\n    Eq,          // ==\n    Ne,          // !=\n    Gte,         // >=\n    Lte,         // <=\n    RegexSearch, // ~=\n    And,         // &&\n    Or,          // ||\n    Coalesce,    // ??\n    DivInt,      // //\n    Pow,         // **\n    Annotate,    // @\n\n    // Aesthetics only\n    Comment(String),\n    DocComment(String),\n    /// Vec containing comments between the newline and the line wrap\n    // Currently we include the comments with the LineWrap token. This isn't\n    // ideal, but I'm not sure of an easy way of having them be separate.\n    // - The line wrap span technically includes the comments — on a newline,\n    //   we need to look ahead to _after_ the comments to see if there's a\n    //   line wrap, and exclude the newline if there is.\n    // - We can only pass one token back\n    //\n    // Alternatives:\n    // - Post-process the stream, removing the newline prior to a line wrap.\n    //   But requires a whole extra pass.\n    // - Change the functionality. But it's very nice to be able to comment\n    //   something out and have line-wraps still work.\n    LineWrap(Vec<TokenKind>),\n\n    /// A token we manually insert at the start of the input, which later stages\n    /// can treat as a newline.\n    Start,\n}\n\n#[derive(\n    Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize, strum::AsRefStr, JsonSchema,\n)]\npub enum Literal {\n    Null,\n    Integer(i64),\n    Float(f64),\n    Boolean(bool),\n    String(String),\n    RawString(String),\n    Date(String),\n    Time(String),\n    Timestamp(String),\n    ValueAndUnit(ValueAndUnit),\n}\n\nimpl TokenKind {\n    pub fn range(bind_left: bool, bind_right: bool) -> Self {\n        TokenKind::Range {\n            bind_left,\n            bind_right,\n        }\n    }\n}\n// Compound units, such as \"2 days 3 hours\" can be represented as `2days + 3hours`\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]\npub struct ValueAndUnit {\n    pub n: i64,       // Do any DBs use floats or decimals for this?\n    pub unit: String, // Could be an enum IntervalType,\n}\n\nimpl std::fmt::Display for Literal {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Literal::Null => write!(f, \"null\")?,\n            Literal::Integer(i) => write!(f, \"{i}\")?,\n            Literal::Float(i) => write!(f, \"{i}\")?,\n\n            Literal::String(s) => {\n                write!(f, \"{}\", quote_string(escape_all_except_quotes(s).as_str()))?;\n            }\n\n            Literal::RawString(s) => {\n                write!(f, \"r{}\", quote_string(s))?;\n            }\n\n            Literal::Boolean(b) => {\n                f.write_str(if *b { \"true\" } else { \"false\" })?;\n            }\n\n            Literal::Date(inner) | Literal::Time(inner) | Literal::Timestamp(inner) => {\n                write!(f, \"@{inner}\")?;\n            }\n\n            Literal::ValueAndUnit(i) => {\n                write!(f, \"{}{}\", i.n, i.unit)?;\n            }\n        }\n        Ok(())\n    }\n}\n\nfn quote_string(s: &str) -> String {\n    if !s.contains('\"') {\n        return format!(r#\"\"{s}\"\"#);\n    }\n\n    if !s.contains('\\'') {\n        return format!(\"'{s}'\");\n    }\n\n    // If the string starts or ends with a quote, use the other quote to delimit\n    // the string. Otherwise default to double quotes.\n\n    // TODO: this doesn't cover a string that starts with a single quote and\n    // ends with a double quote; I think in that case it's necessary to escape\n    // the quote. We need to add tests here.\n\n    let quote = if s.starts_with('\"') || s.ends_with('\"') {\n        '\\''\n    } else {\n        '\"'\n    };\n\n    // When string contains both single and double quotes find the longest\n    // sequence of consecutive quotes, and then use the next highest odd number\n    // of quotes (quotes must be odd; even number of quotes are empty strings).\n    // i.e.:\n    // 0 -> 1\n    // 1 -> 3\n    // 2 -> 3\n    // 3 -> 5\n    let max_consecutive = s\n        .split(|c| c != quote)\n        .map(|quote_sequence| quote_sequence.len())\n        .max()\n        .unwrap_or(0);\n    let next_odd = max_consecutive.div_ceil(2) * 2 + 1;\n    let delim = quote.to_string().repeat(next_odd);\n\n    format!(\"{delim}{s}{delim}\")\n}\n\nfn escape_all_except_quotes(s: &str) -> String {\n    let mut result = String::new();\n    for ch in s.chars() {\n        if ch == '\"' || ch == '\\'' {\n            result.push(ch);\n        } else {\n            result.extend(ch.escape_default());\n        }\n    }\n    result\n}\n\n// This is here because Literal::Float(f64) does not implement Hash, so we cannot simply derive it.\n// There are reasons for that, but chumsky::Error needs Hash for the TokenKind, so it can deduplicate\n// tokens in error.\n// So this hack could lead to duplicated tokens in error messages. Oh no.\n#[allow(clippy::derived_hash_with_manual_eq)]\nimpl std::hash::Hash for TokenKind {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        core::mem::discriminant(self).hash(state);\n    }\n}\n\nimpl std::cmp::Eq for TokenKind {}\n\nimpl std::fmt::Display for TokenKind {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            TokenKind::NewLine => write!(f, \"new line\"),\n            TokenKind::Ident(s) => {\n                if s.is_empty() {\n                    // FYI this shows up in errors\n                    write!(f, \"an identifier\")\n                } else {\n                    write!(f, \"{s}\")\n                }\n            }\n            TokenKind::Keyword(s) => write!(f, \"keyword {s}\"),\n            TokenKind::Literal(lit) => write!(f, \"{lit}\"),\n            TokenKind::Control(c) => write!(f, \"{c}\"),\n\n            TokenKind::ArrowThin => f.write_str(\"->\"),\n            TokenKind::ArrowFat => f.write_str(\"=>\"),\n            TokenKind::Eq => f.write_str(\"==\"),\n            TokenKind::Ne => f.write_str(\"!=\"),\n            TokenKind::Gte => f.write_str(\">=\"),\n            TokenKind::Lte => f.write_str(\"<=\"),\n            TokenKind::RegexSearch => f.write_str(\"~=\"),\n            TokenKind::And => f.write_str(\"&&\"),\n            TokenKind::Or => f.write_str(\"||\"),\n            TokenKind::Coalesce => f.write_str(\"??\"),\n            TokenKind::DivInt => f.write_str(\"//\"),\n            TokenKind::Pow => f.write_str(\"**\"),\n            TokenKind::Annotate => f.write_str(\"@{\"),\n\n            TokenKind::Param(id) => write!(f, \"${id}\"),\n\n            TokenKind::Range {\n                bind_left,\n                bind_right,\n            } => write!(\n                f,\n                \"'{}..{}'\",\n                if *bind_left { \"\" } else { \" \" },\n                if *bind_right { \"\" } else { \" \" }\n            ),\n            TokenKind::Interpolation(c, s) => {\n                write!(f, \"{c}\\\"{s}\\\"\")\n            }\n            TokenKind::Comment(s) => {\n                writeln!(f, \"#{s}\")\n            }\n            TokenKind::DocComment(s) => {\n                writeln!(f, \"#!{s}\")\n            }\n            TokenKind::LineWrap(comments) => {\n                write!(f, \"\\n\\\\ \")?;\n                for comment in comments {\n                    write!(f, \"{comment}\")?;\n                }\n                Ok(())\n            }\n            TokenKind::Start => write!(f, \"start of input\"),\n        }\n    }\n}\n\nimpl std::fmt::Debug for Token {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}..{}: {:?}\", self.span.start, self.span.end, self.kind)\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use insta::assert_snapshot;\n\n    use super::*;\n\n    #[test]\n    fn test_string_quoting() {\n        fn make_str(s: &str) -> Literal {\n            Literal::String(s.to_string())\n        }\n\n        assert_snapshot!(\n            make_str(\"hello\").to_string(),\n            @r#\"\"hello\"\"#\n        );\n\n        assert_snapshot!(\n            make_str(r#\"he's nice\"#).to_string(),\n            @r#\"\"he's nice\"\"#\n        );\n\n        assert_snapshot!(\n            make_str(r#\"he said \"what up\"\"#).to_string(),\n            @r#\"'he said \"what up\"'\"#\n        );\n\n        assert_snapshot!(\n            make_str(r#\"he said \"what's up\"\"#).to_string(),\n            @r#\"'''he said \"what's up\"'''\"#\n        );\n\n        assert_snapshot!(\n            make_str(r#\" single' three double\"\"\" four double\"\"\"\" \"#).to_string(),\n            @r#\"\"\"\"\"\" single' three double\"\"\" four double\"\"\"\" \"\"\"\"\"\"#\n\n        );\n\n        assert_snapshot!(\n            make_str(r#\"\"Starts with a double quote and ' contains a single quote\"#).to_string(),\n            @r#\"'''\"Starts with a double quote and ' contains a single quote'''\"#\n        );\n    }\n\n    #[test]\n    fn test_string_escapes() {\n        assert_snapshot!(\n            Literal::String(r#\"hello\\nworld\"#.to_string()).to_string(),\n            @r#\"\"hello\\\\nworld\"\"#\n        );\n\n        assert_snapshot!(\n            Literal::String(r#\"hello\\tworld\"#.to_string()).to_string(),\n            @r#\"\"hello\\\\tworld\"\"#\n        );\n\n        // TODO: one problem here is that we don't remember whether the original\n        // string contained an actual line break or contained an `\\n` string,\n        // because we immediately normalize both to `\\n`. This means that when\n        // we format the PRQL, we can't retain the original. I think three ways of\n        // resolving this:\n        // - Have different tokens in the lexer and parser; normalize at the\n        //   parsing stage, and then use the token in the lexer for writing out\n        //   the formatted PRQL. Literals are one of the only data structures we\n        //   retain between the lexer and parser. (note that this requires the\n        //   current effort to use tokens from the lexer as part of `prqlc fmt`;\n        //   ongoing as of 2024-08)\n        // - Don't normalize at all, and then normalize when we use the string.\n        //   I think this might be viable and maybe easy, but is a bit less\n        //   elegant; the parser is designed to normalize this sort of thing.\n\n        assert_snapshot!(\n            Literal::String(r#\"hello\n            world\"#.to_string()).to_string(),\n            @r#\"\"hello\\n            world\"\"#\n        );\n    }\n\n    #[test]\n    fn test_raw_string_quoting() {\n        // TODO: add some test for escapes\n        fn make_str(s: &str) -> Literal {\n            Literal::RawString(s.to_string())\n        }\n\n        assert_snapshot!(\n            make_str(\"hello\").to_string(),\n            @r#\"r\"hello\"\"#\n        );\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/lexer/mod.rs",
    "content": "//! PRQL Lexer implementation\n\nuse chumsky;\n\nuse chumsky::extra;\nuse chumsky::prelude::*;\nuse chumsky::Parser;\n\nuse self::lr::{Literal, Token, TokenKind, Tokens, ValueAndUnit};\nuse crate::error::{Error, ErrorSource, Reason, WithErrorInfo};\n\npub mod lr;\n#[cfg(test)]\nmod test;\n\ntype E = Error;\ntype ParserInput<'a> = &'a str;\ntype ParserError<'a> = extra::Err<Simple<'a, char>>;\n\n/// Convert a chumsky Simple error to our internal Error type\nfn convert_lexer_error(source: &str, error: &Simple<'_, char>, source_id: u16) -> E {\n    // Get span information from the Simple error\n    // NOTE: When parsing &str, SimpleSpan uses BYTE offsets, not character offsets!\n    // We need to convert byte offsets to character offsets for compatibility with our error reporting.\n    let byte_span = error.span();\n    let byte_start = byte_span.start();\n    let byte_end = byte_span.end();\n\n    // Convert byte offsets to character offsets\n    let char_start = source[..byte_start].chars().count();\n    let char_end = source[..byte_end].chars().count();\n\n    // Extract the \"found\" text using character-based slicing\n    let found: String = source\n        .chars()\n        .skip(char_start)\n        .take(char_end - char_start)\n        .collect();\n\n    // If found is empty, report as \"end of input\", otherwise wrap in quotes\n    let found_display = if found.is_empty() {\n        \"end of input\".to_string()\n    } else {\n        format!(\"'{}'\", found)\n    };\n\n    // Create a new Error with the extracted information\n    let error_source = format!(\n        \"Unexpected {} at position {}..{}\",\n        found_display, char_start, char_end\n    );\n\n    WithErrorInfo::with_span(\n        Error::new(Reason::Unexpected {\n            found: found_display,\n        }),\n        Some(crate::span::Span {\n            start: char_start,\n            end: char_end,\n            source_id,\n        }),\n    )\n    .with_source(ErrorSource::Lexer(error_source))\n}\n\n/// Lex PRQL into LR, returning both the LR and any errors encountered\npub fn lex_source_recovery(source: &str, source_id: u16) -> (Option<Vec<Token>>, Vec<E>) {\n    let result = lexer().parse(source).into_result();\n\n    match result {\n        Ok(tokens) => (Some(insert_start(tokens.to_vec())), vec![]),\n        Err(errors) => {\n            // Convert chumsky Simple errors to our Error type\n            let errors = errors\n                .into_iter()\n                .map(|error| convert_lexer_error(source, &error, source_id))\n                .collect();\n\n            (None, errors)\n        }\n    }\n}\n\n/// Lex PRQL into LR, returning either the LR or the errors encountered\npub fn lex_source(source: &str) -> Result<Tokens, Vec<E>> {\n    let result = lexer().parse(source).into_result();\n\n    match result {\n        Ok(tokens) => Ok(Tokens(insert_start(tokens.to_vec()))),\n        Err(errors) => {\n            // Convert chumsky Simple errors to our Error type\n            let errors = errors\n                .into_iter()\n                .map(|error| convert_lexer_error(source, &error, 0))\n                .collect();\n\n            Err(errors)\n        }\n    }\n}\n\n/// Insert a start token so later stages can treat the start of a file like a newline\nfn insert_start(tokens: Vec<Token>) -> Vec<Token> {\n    std::iter::once(Token {\n        kind: TokenKind::Start,\n        span: 0..0,\n    })\n    .chain(tokens)\n    .collect()\n}\n\n/// Lex chars to tokens until the end of the input\npub fn lexer<'a>() -> impl Parser<'a, ParserInput<'a>, Vec<Token>, ParserError<'a>> {\n    lex_token()\n        .repeated()\n        .collect()\n        .then_ignore(whitespace().or_not())\n}\n\n/// Lex chars to a single token\nfn lex_token<'a>() -> impl Parser<'a, ParserInput<'a>, Token, ParserError<'a>> {\n    // Handle range token with proper whitespace\n    // Ranges need special handling since the '..' token needs to know about whitespace\n    // for binding on left and right sides\n    let range = whitespace()\n        .or_not()\n        .then(just(\"..\"))\n        .then(whitespace().or_not())\n        .map_with(|((left, _), right), extra| {\n            let span: chumsky::span::SimpleSpan = extra.span();\n            Token {\n                kind: TokenKind::Range {\n                    // Check if there was whitespace before/after to determine binding\n                    bind_left: left.is_none(),\n                    bind_right: right.is_none(),\n                },\n                span: span.start()..span.end(),\n            }\n        });\n\n    // Handle all other token types with proper whitespace\n    let other_tokens = whitespace()\n        .or_not()\n        .ignore_then(token().map_with(|kind, extra| {\n            let span: chumsky::span::SimpleSpan = extra.span();\n            Token {\n                kind,\n                span: span.start()..span.end(),\n            }\n        }));\n\n    // Try to match either a range or any other token\n    choice((range, other_tokens))\n}\n\n/// Parse individual token kinds\nfn token<'a>() -> impl Parser<'a, ParserInput<'a>, TokenKind, ParserError<'a>> {\n    // Main token parser for all tokens\n    // Strategic .boxed() calls reduce compile times for complex parsers with minimal runtime cost\n    choice((\n        line_wrap().boxed(), // Line continuation with backslash (complex recursive)\n        newline().to(TokenKind::NewLine), // Newline characters\n        multi_char_operators(), // Multi-character operators (==, !=, etc.)\n        interpolation().boxed(), // String interpolation (complex nested parsing)\n        param(),             // Parameters ($name)\n        // Date literals must come before @ handling for annotations\n        date_token().boxed(), // Date literals (complex with multiple branches)\n        // Special handling for @ annotations - must come after date_token\n        just('@').to(TokenKind::Annotate), // @ annotation marker\n        one_of(\"></%=+-*[]().,:|!{}\").map(TokenKind::Control), // Single-character controls\n        literal().map(TokenKind::Literal).boxed(), // Literals (complex with many branches)\n        keyword(),                         // Keywords (let, func, etc.)\n        ident_part().map(TokenKind::Ident), // Identifiers\n        comment(),                         // Comments (# and #!)\n    ))\n}\n\nfn multi_char_operators<'a>() -> impl Parser<'a, ParserInput<'a>, TokenKind, ParserError<'a>> {\n    choice((\n        just(\"->\").to(TokenKind::ArrowThin),\n        just(\"=>\").to(TokenKind::ArrowFat),\n        just(\"==\").to(TokenKind::Eq),\n        just(\"!=\").to(TokenKind::Ne),\n        just(\">=\").to(TokenKind::Gte),\n        just(\"<=\").to(TokenKind::Lte),\n        just(\"~=\").to(TokenKind::RegexSearch),\n        just(\"&&\").then_ignore(end_expr()).to(TokenKind::And),\n        just(\"||\").then_ignore(end_expr()).to(TokenKind::Or),\n        just(\"??\").to(TokenKind::Coalesce),\n        just(\"//\").to(TokenKind::DivInt),\n        just(\"**\").to(TokenKind::Pow),\n    ))\n}\n\nfn keyword<'a>() -> impl Parser<'a, ParserInput<'a>, TokenKind, ParserError<'a>> {\n    choice((\n        just(\"let\"),\n        just(\"into\"),\n        just(\"case\"),\n        just(\"prql\"),\n        just(\"type\"),\n        just(\"module\"),\n        just(\"internal\"),\n        just(\"func\"),\n        just(\"import\"),\n        just(\"enum\"),\n    ))\n    .to_slice()\n    .then_ignore(end_expr())\n    .map(|s: &str| TokenKind::Keyword(s.to_string()))\n}\n\nfn param<'a>() -> impl Parser<'a, ParserInput<'a>, TokenKind, ParserError<'a>> {\n    just('$')\n        .ignore_then(\n            any()\n                .filter(|c: &char| c.is_alphanumeric() || *c == '_' || *c == '.')\n                .repeated()\n                .to_slice()\n                .map(|s: &str| s.to_string()),\n        )\n        .map(TokenKind::Param)\n}\n\nfn interpolation<'a>() -> impl Parser<'a, ParserInput<'a>, TokenKind, ParserError<'a>> {\n    // For s-strings and f-strings, use the same multi-quote string parser\n    // Enable escaping so that `\\\"` in the source becomes a literal `\"` in the string\n    //\n    // NOTE: Known limitation in error reporting for unclosed interpolated strings:\n    // When an f-string or s-string is unclosed (e.g., `f\"{}`), the error is reported at the\n    // opening quote position (e.g., position 17) rather than at the end of input where the\n    // closing quote should be (e.g., position 20). This is because the `.then()` combinator\n    // modifies error spans during error recovery, and there's no way to prevent this from\n    // custom parsers.\n    one_of(\"sf\")\n        .then(quoted_string(true))\n        .map(|(c, s)| TokenKind::Interpolation(c, s))\n}\n\nfn whitespace<'a>() -> impl Parser<'a, ParserInput<'a>, (), ParserError<'a>> {\n    text::inline_whitespace().at_least(1)\n}\n\n// Custom newline parser for Stream<char>\nfn newline<'a>() -> impl Parser<'a, ParserInput<'a>, (), ParserError<'a>> {\n    just('\\n')\n        .or(just('\\r').then_ignore(just('\\n').or_not()))\n        .ignored()\n}\n\nfn line_wrap<'a>() -> impl Parser<'a, ParserInput<'a>, TokenKind, ParserError<'a>> {\n    newline()\n        .ignore_then(\n            whitespace()\n                .repeated()\n                .ignore_then(comment())\n                .then_ignore(newline())\n                .repeated()\n                .collect(),\n        )\n        .then_ignore(whitespace().repeated())\n        .then_ignore(just('\\\\'))\n        .map(TokenKind::LineWrap)\n}\n\nfn comment<'a>() -> impl Parser<'a, ParserInput<'a>, TokenKind, ParserError<'a>> {\n    // Extract the common comment text parser\n    let comment_text = none_of(\"\\n\\r\").repeated().collect::<String>();\n\n    just('#').ignore_then(\n        // One option would be to check that doc comments have new lines in the\n        // lexer (we currently do in the parser); which would give better error\n        // messages?\n        just('!')\n            .ignore_then(comment_text.map(TokenKind::DocComment))\n            .or(comment_text.map(TokenKind::Comment)),\n    )\n}\n\npub fn ident_part<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserError<'a>> {\n    let plain = any()\n        .filter(|c: &char| c.is_alphabetic() || *c == '_')\n        .then(\n            // this could _almost_ just be, but we don't currently allow numbers\n            // (should we?)\n            //\n            // .then(text::ascii::ident())\n            any()\n                .filter(|c: &char| c.is_alphanumeric() || *c == '_')\n                .repeated(),\n        )\n        .to_slice()\n        .map(|s: &str| s.to_string());\n\n    let backtick = none_of('`')\n        .repeated()\n        .collect::<String>()\n        .delimited_by(just('`'), just('`'));\n\n    choice((plain, backtick))\n}\n\n// Date/time components\nfn digits<'a>(count: usize) -> impl Parser<'a, ParserInput<'a>, &'a str, ParserError<'a>> {\n    chumsky::text::digits(10).exactly(count).to_slice()\n}\n\nfn date_inner<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserError<'a>> {\n    // Format: YYYY-MM-DD\n    text::digits(10)\n        .exactly(4)\n        .then(just('-'))\n        .then(text::digits(10).exactly(2))\n        .then(just('-'))\n        .then(text::digits(10).exactly(2))\n        .to_slice()\n        // TODO: Returning &str instead of String would require changing Literal::Date\n        // to use Cow<'a, str> or a similar approach, which is a larger refactoring\n        .map(|s: &str| s.to_owned())\n}\n\nfn time_inner<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserError<'a>> {\n    // Helper function for parsing time components with separators\n    fn time_component<'p>(\n        separator: char,\n        component_parser: impl Parser<'p, ParserInput<'p>, &'p str, ParserError<'p>>,\n    ) -> impl Parser<'p, ParserInput<'p>, String, ParserError<'p>> {\n        just(separator)\n            .then(component_parser)\n            .map(move |(sep, comp): (char, &str)| format!(\"{}{}\", sep, comp))\n            .or_not()\n            .map(|opt| opt.unwrap_or_default())\n    }\n\n    // Hours (required)\n    let hours = digits(2).map(|s: &str| s.to_string());\n\n    // Minutes and seconds (optional) - with colon separator\n    let minutes = time_component(':', digits(2));\n    let seconds = time_component(':', digits(2));\n\n    // Milliseconds (optional) - with dot separator\n    let milliseconds = time_component(\n        '.',\n        any()\n            .filter(|c: &char| c.is_ascii_digit())\n            .repeated()\n            .at_least(1)\n            .at_most(6)\n            .to_slice(),\n    );\n\n    // Timezone (optional): either 'Z' or '+/-HH:MM'\n    let timezone = choice((\n        just('Z').map(|c| c.to_string()),\n        one_of(\"-+\")\n            .then(digits(2).then(just(':').or_not().then(digits(2))).map(\n                |(hrs, (_opt_colon, mins)): (&str, (Option<char>, &str))| {\n                    // Always format as -0800 without colon for SQL compatibility, regardless of input format\n                    // We need to handle both -08:00 and -0800 input formats but standardize the output\n                    format!(\"{}{}\", hrs, mins)\n                },\n            ))\n            .map(|(sign, offset)| format!(\"{}{}\", sign, offset)),\n    ))\n    .or_not()\n    .map(|opt| opt.unwrap_or_default());\n\n    // Combine all parts\n    hours\n        .then(minutes)\n        .then(seconds)\n        .then(milliseconds)\n        .then(timezone)\n        .map(|((((hours, mins), secs), ms), tz)| format!(\"{}{}{}{}{}\", hours, mins, secs, ms, tz))\n}\n\nfn date_token<'a>() -> impl Parser<'a, ParserInput<'a>, TokenKind, ParserError<'a>> {\n    // Match digit after @ for date/time literals\n    just('@')\n        // The next character should be a digit\n        .then(any().filter(|c: &char| c.is_ascii_digit()).rewind())\n        .ignore_then(\n            // Once we know it's a date/time literal (@ followed by a digit),\n            // parse the three possible formats\n            choice((\n                // Datetime: @2022-01-01T12:00\n                date_inner()\n                    .then(just('T'))\n                    .then(time_inner())\n                    .then_ignore(end_expr())\n                    .map(|((date, t), time)| Literal::Timestamp(format!(\"{}{}{}\", date, t, time))),\n                // Date: @2022-01-01\n                date_inner().then_ignore(end_expr()).map(Literal::Date),\n                // Time: @12:00\n                time_inner().then_ignore(end_expr()).map(Literal::Time),\n            )),\n        )\n        .map(TokenKind::Literal)\n}\n\npub fn literal<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    choice((\n        binary_number(),\n        hexadecimal_number(),\n        octal_number(),\n        string(),\n        raw_string(),\n        value_and_unit(),\n        number(),\n        boolean(),\n        null(),\n    ))\n}\n\n// Helper to create number parsers with different bases\nfn parse_number_with_base<'a>(\n    prefix: &'static str,\n    base: u32,\n    max_digits: usize,\n    valid_digit: impl Fn(&char) -> bool + 'a,\n) -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    just(prefix)\n        .then_ignore(just(\"_\").or_not()) // Optional underscore after prefix\n        .ignore_then(\n            any()\n                .filter(valid_digit)\n                .repeated()\n                .at_least(1)\n                .at_most(max_digits)\n                .to_slice()\n                .map(move |digits: &str| {\n                    i64::from_str_radix(digits, base)\n                        .map(Literal::Integer)\n                        .unwrap_or(Literal::Integer(0))\n                }),\n        )\n}\n\nfn binary_number<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    parse_number_with_base(\"0b\", 2, 32, |c| *c == '0' || *c == '1')\n}\n\nfn hexadecimal_number<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    parse_number_with_base(\"0x\", 16, 12, |c| c.is_ascii_hexdigit())\n}\n\nfn octal_number<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    parse_number_with_base(\"0o\", 8, 12, |c| ('0'..='7').contains(c))\n}\n\nfn number<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    // Helper function to build a string from optional number components\n    fn optional_component<'p, T>(\n        parser: impl Parser<'p, ParserInput<'p>, T, ParserError<'p>>,\n        to_string: impl Fn(T) -> String + 'p,\n    ) -> impl Parser<'p, ParserInput<'p>, String, ParserError<'p>> {\n        parser\n            .map(to_string)\n            .or_not()\n            .map(|opt| opt.unwrap_or_default())\n    }\n\n    // Parse integer part\n    let integer = parse_integer();\n\n    // Parse fractional part\n    let fraction_digits = any()\n        .filter(|c: &char| c.is_ascii_digit())\n        .then(\n            any()\n                .filter(|c: &char| c.is_ascii_digit() || *c == '_')\n                .repeated(),\n        )\n        .to_slice();\n\n    let frac = just('.')\n        .then(fraction_digits)\n        .map(|(dot, digits): (char, &str)| format!(\"{}{}\", dot, digits));\n\n    // Parse exponent\n    let exp_digits = one_of(\"+-\")\n        .or_not()\n        .then(\n            any()\n                .filter(|c: &char| c.is_ascii_digit())\n                .repeated()\n                .at_least(1),\n        )\n        .to_slice();\n\n    let exp = one_of(\"eE\")\n        .then(exp_digits)\n        .map(|(e, digits): (char, &str)| format!(\"{}{}\", e, digits));\n\n    // Combine all parts into a number using the helper function\n    integer\n        .then(optional_component(frac, |f| f))\n        .then(optional_component(exp, |e| e))\n        .map(|((int_part, frac_part), exp_part)| {\n            // Construct the number string and remove underscores\n            let num_str = format!(\"{}{}{}\", int_part, frac_part, exp_part)\n                .chars()\n                .filter(|&c| c != '_')\n                .collect::<String>();\n\n            // Try to parse as integer first, then as float\n            if let Ok(i) = num_str.parse::<i64>() {\n                Literal::Integer(i)\n            } else if let Ok(f) = num_str.parse::<f64>() {\n                Literal::Float(f)\n            } else {\n                Literal::Integer(0) // Fallback\n            }\n        })\n}\n\nfn parse_integer<'a>() -> impl Parser<'a, ParserInput<'a>, &'a str, ParserError<'a>> {\n    // Handle both multi-digit numbers (can't start with 0) and single digit 0\n    choice((\n        any()\n            .filter(|c: &char| c.is_ascii_digit() && *c != '0')\n            .then(\n                any()\n                    .filter(|c: &char| c.is_ascii_digit() || *c == '_')\n                    .repeated(),\n            )\n            .to_slice(),\n        just('0').to_slice(),\n    ))\n}\n\nfn string<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    quoted_string(true).map(Literal::String)\n}\n\nfn raw_string<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    just(\"r\")\n        .then(choice((just('\\''), just('\"'))))\n        .then(\n            any()\n                .filter(move |c: &char| *c != '\\'' && *c != '\"' && *c != '\\n' && *c != '\\r')\n                .repeated()\n                .to_slice(),\n        )\n        .then(choice((just('\\''), just('\"'))))\n        .map(\n            |(((_, _open_quote), s), _close_quote): (((&str, char), &str), char)| {\n                Literal::RawString(s.to_string())\n            },\n        )\n}\n\nfn boolean<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    choice((just(\"true\").to(true), just(\"false\").to(false)))\n        .then_ignore(end_expr())\n        .map(Literal::Boolean)\n}\n\nfn null<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    just(\"null\").to(Literal::Null).then_ignore(end_expr())\n}\n\nfn value_and_unit<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserError<'a>> {\n    // Supported time units\n    let unit = choice((\n        just(\"microseconds\"),\n        just(\"milliseconds\"),\n        just(\"seconds\"),\n        just(\"minutes\"),\n        just(\"hours\"),\n        just(\"days\"),\n        just(\"weeks\"),\n        just(\"months\"),\n        just(\"years\"),\n    ));\n\n    // Parse the integer value followed by a unit\n    parse_integer().then(unit).then_ignore(end_expr()).map(\n        |(number_str, unit_str): (&str, &str)| {\n            // Parse the number (removing underscores), defaulting to 1 if parsing fails\n            let n = number_str.replace('_', \"\").parse::<i64>().unwrap_or(1);\n            Literal::ValueAndUnit(ValueAndUnit {\n                n,\n                unit: unit_str.to_string(),\n            })\n        },\n    )\n}\n\npub fn quoted_string<'a>(\n    escaped: bool,\n) -> impl Parser<'a, ParserInput<'a>, String, ParserError<'a>> {\n    choice((\n        multi_quoted_string(&'\"', escaped),\n        multi_quoted_string(&'\\'', escaped),\n    ))\n    .map(|chars| chars.into_iter().collect())\n}\n\n// Helper function to parse escape sequences\n// Takes the input and the quote character, returns the escaped character\nfn parse_escape_sequence<'a>(\n    input: &mut chumsky::input::InputRef<'a, '_, ParserInput<'a>, ParserError<'a>>,\n    quote_char: char,\n) -> char {\n    match input.peek() {\n        Some(next_ch) => {\n            input.next();\n            match next_ch {\n                '\\\\' => '\\\\',\n                '/' => '/',\n                'b' => '\\x08',\n                'f' => '\\x0C',\n                'n' => '\\n',\n                'r' => '\\r',\n                't' => '\\t',\n                'u' if input.peek() == Some('{') => {\n                    input.next(); // consume '{'\n                    let mut hex = String::new();\n                    while let Some(ch) = input.peek() {\n                        if ch == '}' {\n                            input.next();\n                            break;\n                        }\n                        if ch.is_ascii_hexdigit() && hex.len() < 6 {\n                            hex.push(ch);\n                            input.next();\n                        } else {\n                            break;\n                        }\n                    }\n                    char::from_u32(u32::from_str_radix(&hex, 16).unwrap_or(0)).unwrap_or('\\u{FFFD}')\n                }\n                'x' => {\n                    let mut hex = String::new();\n                    for _ in 0..2 {\n                        if let Some(ch) = input.peek() {\n                            if ch.is_ascii_hexdigit() {\n                                hex.push(ch);\n                                input.next();\n                            }\n                        }\n                    }\n                    if hex.len() == 2 {\n                        char::from_u32(u32::from_str_radix(&hex, 16).unwrap_or(0))\n                            .unwrap_or('\\u{FFFD}')\n                    } else {\n                        next_ch // Just use the character after backslash\n                    }\n                }\n                c if c == quote_char => quote_char, // Escaped quote\n                other => other,                     // Unknown escape, keep the character\n            }\n        }\n        None => {\n            // Backslash at end of input\n            '\\\\'\n        }\n    }\n}\n\n// Implementation of multi-level quoted strings using custom parser\n// Handles odd number of quotes (1, 3, 5, etc.) for strings with content\n// and even number of quotes (2, 4, 6, etc.) for empty strings\n//\n// This uses a single custom parser that dynamically handles arbitrary quote counts\n// All quoted strings allow newlines\nfn multi_quoted_string<'a>(\n    quote: &char,\n    escaping: bool,\n) -> impl Parser<'a, ParserInput<'a>, Vec<char>, ParserError<'a>> {\n    let quote_char = *quote;\n\n    custom(move |input| {\n        let start_cursor = input.save();\n\n        // Count opening quotes\n        let mut open_count = 0;\n        while let Some(ch) = input.peek() {\n            if ch == quote_char {\n                input.next();\n                open_count += 1;\n            } else {\n                break;\n            }\n        }\n\n        if open_count == 0 {\n            let span = input.span_since(start_cursor.cursor());\n            return Err(Simple::new(input.peek_maybe(), span));\n        }\n\n        // Even number of quotes -> empty string\n        if open_count % 2 == 0 {\n            return Ok(vec![]);\n        }\n\n        // Odd number of quotes -> parse content until we find the closing delimiter\n        let mut result = Vec::new();\n\n        loop {\n            // Save position to potentially rewind\n            let checkpoint = input.save();\n\n            // Try to match the closing delimiter (open_count quotes)\n            let mut close_count = 0;\n            while close_count < open_count {\n                match input.peek() {\n                    Some(ch) if ch == quote_char => {\n                        input.next();\n                        close_count += 1;\n                    }\n                    _ => break,\n                }\n            }\n\n            // If we matched the full delimiter, we're done\n            if close_count == open_count {\n                return Ok(result);\n            }\n\n            // Not the delimiter - rewind and consume one content character\n            input.rewind(checkpoint);\n\n            match input.next() {\n                Some(ch) => {\n                    // Handle escape sequences if escaping is enabled\n                    if escaping && ch == '\\\\' {\n                        let escaped = parse_escape_sequence(input, quote_char);\n                        result.push(escaped);\n                    } else {\n                        result.push(ch);\n                    }\n                }\n                None => {\n                    // Can't find closing delimiter - return error about unclosed string\n                    // Create a zero-width span at the current position (end of input)\n                    let current_cursor = input.save();\n                    let span = input.span_since(current_cursor.cursor());\n                    return Err(Simple::new(None, span));\n                }\n            }\n        }\n    })\n}\n\nfn end_expr<'a>() -> impl Parser<'a, ParserInput<'a>, (), ParserError<'a>> {\n    choice((\n        end(),\n        one_of(\",)]}\\t >\").to(()),\n        newline(),\n        just(\"..\").to(()),\n    ))\n    .rewind()\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/lexer/test.rs",
    "content": "use chumsky;\n\nuse chumsky::Parser;\nuse insta::assert_debug_snapshot;\nuse insta::assert_snapshot;\n\nuse crate::lexer::lex_source;\nuse crate::lexer::lr::{Literal, TokenKind, Tokens};\nuse crate::lexer::{lexer, literal, quoted_string};\n\n#[test]\nfn line_wrap() {\n    fn test_line_wrap_tokens(input: &str) -> Tokens {\n        Tokens(lexer().parse(input).output().unwrap().to_vec())\n    }\n\n    assert_eq!(\n        format!(\n            \"{}\",\n            TokenKind::LineWrap(vec![TokenKind::Comment(\" a comment\".to_string())])\n        ),\n        r#\"\n\\ # a comment\n\"#\n    );\n\n    // Basic line wrap test\n    assert_debug_snapshot!(test_line_wrap_tokens(r\"5 +\n    \\ 3 \"), @r\"\n    Tokens(\n        [\n            0..1: Literal(Integer(5)),\n            2..3: Control('+'),\n            3..9: LineWrap([]),\n            10..11: Literal(Integer(3)),\n        ],\n    )\n    \");\n\n    // Comments in line wrap test\n    assert_debug_snapshot!(test_line_wrap_tokens(r\"5 +\n# comment\n   # comment with whitespace\n  \\ 3 \"), @r#\"\n    Tokens(\n        [\n            0..1: Literal(Integer(5)),\n            2..3: Control('+'),\n            3..46: LineWrap([Comment(\" comment\"), Comment(\" comment with whitespace\")]),\n            47..48: Literal(Integer(3)),\n        ],\n    )\n    \"#);\n}\n\n#[test]\nfn numbers() {\n    fn test_number_parsing(input: &str, expected: Literal) {\n        assert_eq!(literal().parse(input).output().unwrap(), &expected);\n    }\n\n    // Binary notation\n    test_number_parsing(\"0b1111000011110000\", Literal::Integer(61680));\n    test_number_parsing(\"0b_1111000011110000\", Literal::Integer(61680));\n\n    // Hexadecimal notation\n    test_number_parsing(\"0xff\", Literal::Integer(255));\n    test_number_parsing(\"0x_deadbeef\", Literal::Integer(3735928559));\n\n    // Octal notation\n    test_number_parsing(\"0o777\", Literal::Integer(511));\n}\n\n#[test]\nfn debug_display() {\n    fn test_tokens(input: &str) -> Tokens {\n        Tokens(lexer().parse(input).output().unwrap().to_vec())\n    }\n\n    assert_debug_snapshot!(test_tokens(\"5 + 3\"), @r\"\n    Tokens(\n        [\n            0..1: Literal(Integer(5)),\n            2..3: Control('+'),\n            4..5: Literal(Integer(3)),\n        ],\n    )\n    \");\n}\n\n#[test]\nfn comment() {\n    // The format rendering test can be shared since it's independent of Chumsky\n    assert_snapshot!(TokenKind::Comment(\" This is a single-line comment\".to_string()), \n                    @\"# This is a single-line comment\");\n\n    // For the parser test, we use a unified function\n    fn test_comment_tokens(input: &str) -> Tokens {\n        Tokens(lexer().parse(input).output().unwrap().to_vec())\n    }\n\n    assert_debug_snapshot!(test_comment_tokens(\"# comment\\n# second line\"), @r#\"\n    Tokens(\n        [\n            0..9: Comment(\" comment\"),\n            9..10: NewLine,\n            10..23: Comment(\" second line\"),\n        ],\n    )\n    \"#);\n}\n\n#[test]\nfn doc_comment() {\n    // Unified function to test doccomment tokens\n    fn test_doc_comment_tokens(input: &str) -> Tokens {\n        Tokens(lexer().parse(input).output().unwrap().to_vec())\n    }\n\n    assert_debug_snapshot!(test_doc_comment_tokens(\"#! docs\"), @r#\"\n    Tokens(\n        [\n            0..7: DocComment(\" docs\"),\n        ],\n    )\n    \"#);\n}\n\n#[test]\nfn quotes() {\n    fn test_basic_string(input: &str, escaped: bool, expected_str: &str) {\n        let parse_result = quoted_string(escaped).parse(input);\n        if let Some(result) = parse_result.output() {\n            assert_eq!(result, expected_str);\n        } else {\n            panic!(\"Failed to parse string: {:?}\", input);\n        }\n    }\n\n    test_basic_string(r#\"'aoeu'\"#, false, \"aoeu\");\n    test_basic_string(r#\"''\"#, true, \"\");\n    test_basic_string(r#\"\"hello\"\"#, true, \"hello\");\n    test_basic_string(r#\"\"hello\\nworld\"\"#, true, \"hello\\nworld\");\n\n    // Test escaped quotes\n    let basic_escaped = r#\"\"hello\\\\\"\"#; // Test just a backslash escape\n    test_basic_string(basic_escaped, true, \"hello\\\\\");\n\n    // Triple-quoted string tests\n    test_basic_string(r#\"'''aoeu'''\"#, false, \"aoeu\");\n    test_basic_string(r#\"\"\"\"aoeu\"\"\"\"#, true, \"aoeu\");\n\n    // Add more tests for our implementation\n    test_basic_string(r#\"\"hello world\"\"#, true, \"hello world\");\n}\n\n#[test]\nfn interpolated_strings() {\n    // Helper function to test interpolated string tokens\n    fn test_interpolation_tokens(input: &str) -> Tokens {\n        Tokens(lexer().parse(input).output().unwrap().to_vec())\n    }\n\n    // Test s-string and f-string with regular quotes\n    assert_debug_snapshot!(test_interpolation_tokens(r#\"s\"Hello {name}\"\"#), @r#\"\n    Tokens(\n        [\n            0..15: Interpolation('s', \"Hello {name}\"),\n        ],\n    )\n    \"#);\n\n    // Test s-string with triple quotes (important for multi-line SQL in s-strings)\n    assert_debug_snapshot!(test_interpolation_tokens(r#\"s\"\"\"SELECT * FROM table WHERE id = {id}\"\"\" \"#), @r#\"\n    Tokens(\n        [\n            0..42: Interpolation('s', \"SELECT * FROM table WHERE id = {id}\"),\n        ],\n    )\n    \"#);\n\n    // Test s-string with escaped quotes (issue #5494 regression)\n    assert_debug_snapshot!(test_interpolation_tokens(r#\"s\"SELECT \\\"col1 foo\\\"\"\"#), @r#\"\n    Tokens(\n        [\n            0..22: Interpolation('s', \"SELECT \\\"col1 foo\\\"\"),\n        ],\n    )\n    \"#);\n}\n\n#[test]\nfn timestamp_tests() {\n    // Helper function to test tokens with timestamps\n    fn test_timestamp_tokens(input: &str) -> Tokens {\n        Tokens(lexer().parse(input).output().unwrap().to_vec())\n    }\n\n    // Test timestamp with timezone format -08:00 (with colon)\n    assert_debug_snapshot!(test_timestamp_tokens(\"@2020-01-01T13:19:55-08:00\"), @r#\"\n    Tokens(\n        [\n            0..26: Literal(Timestamp(\"2020-01-01T13:19:55-0800\")),\n        ],\n    )\n    \"#);\n\n    // Test timestamp with timezone format Z\n    assert_debug_snapshot!(test_timestamp_tokens(\"@2020-01-02T21:19:55Z\"), @r#\"\n    Tokens(\n        [\n            0..21: Literal(Timestamp(\"2020-01-02T21:19:55Z\")),\n        ],\n    )\n    \"#);\n}\n\n#[test]\nfn range() {\n    fn test_range_tokens(input: &str) -> Tokens {\n        Tokens(lexer().parse(input).output().unwrap().to_vec())\n    }\n\n    assert_debug_snapshot!(test_range_tokens(\"1..2\"), @r\"\n    Tokens(\n        [\n            0..1: Literal(Integer(1)),\n            1..3: Range { bind_left: true, bind_right: true },\n            3..4: Literal(Integer(2)),\n        ],\n    )\n    \");\n\n    assert_debug_snapshot!(test_range_tokens(\"..2\"), @r\"\n    Tokens(\n        [\n            0..2: Range { bind_left: true, bind_right: true },\n            2..3: Literal(Integer(2)),\n        ],\n    )\n    \");\n\n    assert_debug_snapshot!(test_range_tokens(\"1..\"), @r\"\n    Tokens(\n        [\n            0..1: Literal(Integer(1)),\n            1..3: Range { bind_left: true, bind_right: true },\n        ],\n    )\n    \");\n\n    let result = test_range_tokens(\"in ..5\");\n\n    // Just verify we have 3 tokens, with the right types and values\n    assert_eq!(result.0.len(), 3);\n\n    // Check token types\n    assert!(matches!(result.0[0].kind, TokenKind::Ident(ref s) if s == \"in\"));\n    assert!(matches!(result.0[1].kind, TokenKind::Range { .. }));\n    assert!(matches!(\n        result.0[2].kind,\n        TokenKind::Literal(Literal::Integer(5))\n    ));\n}\n\n#[test]\nfn test_lex_source() {\n    use insta::assert_debug_snapshot;\n\n    assert_debug_snapshot!(lex_source(\"5 + 3\"), @r\"\n    Ok(\n        Tokens(\n            [\n                0..0: Start,\n                0..1: Literal(Integer(5)),\n                2..3: Control('+'),\n                4..5: Literal(Integer(3)),\n            ],\n        ),\n    )\n    \");\n\n    let result = lex_source(\"^\");\n    assert!(result.is_err());\n}\n\n#[test]\nfn test_annotation_tokens() {\n    use insta::assert_debug_snapshot;\n\n    // Test basic annotation token\n    let result = super::lex_source(\"@{binding_strength=1}\");\n    assert_debug_snapshot!(result, @r#\"\n        Ok(\n            Tokens(\n                [\n                    0..0: Start,\n                    0..1: Annotate,\n                    1..2: Control('{'),\n                    2..18: Ident(\"binding_strength\"),\n                    18..19: Control('='),\n                    19..20: Literal(Integer(1)),\n                    20..21: Control('}'),\n                ],\n            ),\n        )\n        \"#);\n\n    // Test multi-line annotation\n    let result = super::lex_source(\n        r#\"\n        @{binding_strength=1}\n        let add = a b -> a + b\n        \"#,\n    );\n    assert_debug_snapshot!(result, @r#\"\n        Ok(\n            Tokens(\n                [\n                    0..0: Start,\n                    0..1: NewLine,\n                    9..10: Annotate,\n                    10..11: Control('{'),\n                    11..27: Ident(\"binding_strength\"),\n                    27..28: Control('='),\n                    28..29: Literal(Integer(1)),\n                    29..30: Control('}'),\n                    30..31: NewLine,\n                    39..42: Keyword(\"let\"),\n                    43..46: Ident(\"add\"),\n                    47..48: Control('='),\n                    49..50: Ident(\"a\"),\n                    51..52: Ident(\"b\"),\n                    53..55: ArrowThin,\n                    56..57: Ident(\"a\"),\n                    58..59: Control('+'),\n                    60..61: Ident(\"b\"),\n                    61..62: NewLine,\n                ],\n            ),\n        )\n        \"#);\n}\n\n#[test]\nfn test_issue_triple_quoted_with_double_quote() {\n    use insta::assert_debug_snapshot;\n\n    // The specific test case from ISSUE.md that was failing\n    let input = r#\"\"\"\"\n''\nCanada\n\"\n\n\"\"\"\"#;\n    let result = super::lex_source(input);\n    eprintln!(\"Result: {:#?}\", result);\n    assert_debug_snapshot!(result, @r#\"\n    Ok(\n        Tokens(\n            [\n                0..0: Start,\n                0..20: Literal(String(\"\\n''\\nCanada\\n\\\"\\n\\n\")),\n            ],\n        ),\n    )\n    \"#);\n}\n\n#[test]\nfn test_single_curly_quote() {\n    use insta::assert_debug_snapshot;\n\n    // Test what error we get for a single curly quote character\n    let input = \"’\"; // U+2019 RIGHT SINGLE QUOTATION MARK\n\n    eprintln!(\"\\n=== Single Curly Quote Test ===\");\n    eprintln!(\"Input: {:?}\", input);\n    eprintln!(\"Input bytes: {:?}\", input.as_bytes());\n    eprintln!(\n        \"Char 0: {:?} (U+{:04X})\",\n        input.chars().next().unwrap(),\n        input.chars().next().unwrap() as u32\n    );\n\n    let result = lex_source(input);\n    eprintln!(\"Result: {:#?}\", result);\n\n    assert_debug_snapshot!(result, @r#\"\n    Err(\n        [\n            Error {\n                kind: Error,\n                span: Some(\n                    0:0-1,\n                ),\n                reason: Unexpected {\n                    found: \"'’'\",\n                },\n                hints: [],\n                code: None,\n            },\n        ],\n    )\n    \"#);\n}\n\n#[test]\nfn test_mississippi_curly_quotes() {\n    use insta::assert_debug_snapshot;\n\n    // Test error reporting for curly quotes (U+2019)\n    // This is the Mississippi test case from integration tests\n    // NOTE: The quotes in this string are U+2019 RIGHT SINGLE QUOTATION MARK (curly quotes),\n    // not U+0027 APOSTROPHE. Make sure your editor preserves them!\n    let input = \"Mississippi has four S’s and four I’s.\";\n\n    eprintln!(\"\\n=== Mississippi Curly Quotes Test ===\");\n    eprintln!(\"Input: {:?}\", input);\n    eprintln!(\"Input bytes: {:?}\", input.as_bytes());\n    eprintln!(\n        \"Char 22: {:?} (U+{:04X})\",\n        input.chars().nth(22).unwrap(),\n        input.chars().nth(22).unwrap() as u32\n    );\n    eprintln!(\n        \"Char 35: {:?} (U+{:04X})\",\n        input.chars().nth(35).unwrap(),\n        input.chars().nth(35).unwrap() as u32\n    );\n\n    let result1 = lex_source(input);\n    eprintln!(\"{:#?}\", result1);\n\n    let (tokens, errors) = super::lex_source_recovery(input, 1);\n    eprintln!(\"Tokens: {:#?}\", tokens);\n    eprintln!(\"Errors: {:#?}\", errors);\n\n    assert_debug_snapshot!(result1, @r#\"\n    Err(\n        [\n            Error {\n                kind: Error,\n                span: Some(\n                    0:22-23,\n                ),\n                reason: Unexpected {\n                    found: \"'’'\",\n                },\n                hints: [],\n                code: None,\n            },\n        ],\n    )\n    \"#);\n}\n\n#[test]\nfn test_interpolation_empty() {\n    use insta::assert_debug_snapshot;\n\n    // Test the f\"{}\" case that's showing a changed error position\n    let input = r#\"from x | select f\"{}\"#;\n\n    eprintln!(\"\\n=== Interpolation Empty Test ===\");\n    eprintln!(\"Input: {:?}\", input);\n    eprintln!(\"Input bytes: {:?}\", input.as_bytes());\n    eprintln!(\n        \"Input length: {} bytes, {} chars\",\n        input.len(),\n        input.chars().count()\n    );\n\n    let result = lex_source(input);\n    eprintln!(\"lex_source result: {:#?}\", result);\n\n    let (tokens, errors) = super::lex_source_recovery(input, 1);\n    eprintln!(\"lex_source_recovery tokens: {:#?}\", tokens);\n    eprintln!(\"lex_source_recovery errors: {:#?}\", errors);\n\n    assert_debug_snapshot!(result, @r#\"\n    Err(\n        [\n            Error {\n                kind: Error,\n                span: Some(\n                    0:20-20,\n                ),\n                reason: Unexpected {\n                    found: \"end of input\",\n                },\n                hints: [],\n                code: None,\n            },\n        ],\n    )\n    \"#);\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/lib.rs",
    "content": "pub mod error;\npub mod generic;\npub mod lexer;\npub mod parser;\npub mod span;\n#[cfg(test)]\npub(crate) mod test;\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/expr.rs",
    "content": "use std::collections::{hash_map::Entry, HashMap};\n\nuse chumsky;\nuse chumsky::input::BorrowInput;\nuse chumsky::pratt::*;\nuse chumsky::prelude::*;\nuse itertools::Itertools;\n\nuse crate::lexer::lr;\nuse crate::lexer::lr::TokenKind;\nuse crate::parser::interpolation;\nuse crate::parser::pr::*;\nuse crate::parser::types::type_expr;\nuse crate::parser::{ctrl, ident_part, keyword, new_line, sequence, with_doc_comment};\nuse crate::span::Span;\n\nuse super::pipe;\nuse super::ParserError;\n\npub(crate) fn expr_call<'a, I>() -> impl Parser<'a, I, Expr, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    let expr = expr();\n\n    choice((\n        lambda_func(expr.clone()).boxed(),\n        func_call(expr.clone()).boxed(),\n        pipeline(expr).boxed(),\n    ))\n    .boxed()\n}\n\npub(crate) fn expr<'a, I>() -> impl Parser<'a, I, Expr, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    recursive(|expr| {\n        let literal = select_ref! { lr::Token { kind: TokenKind::Literal(lit), .. } => ExprKind::Literal(lit.clone()) };\n\n        let ident_kind = ident().map(ExprKind::Ident);\n\n        let internal = keyword(\"internal\")\n            .ignore_then(ident())\n            .map(|x| x.to_string())\n            .map(ExprKind::Internal);\n\n        let nested_expr = with_doc_comment(\n            lambda_func(expr.clone())\n                .or(func_call(expr.clone()))\n                .boxed(),\n        );\n\n        let tuple = tuple(nested_expr.clone());\n        let array = array(nested_expr.clone());\n        let pipeline_expr = {\n            use chumsky::recovery::{skip_then_retry_until, via_parser};\n\n            pipeline(nested_expr.clone())\n                .padded_by(new_line().repeated())\n                .delimited_by(\n                    ctrl('('),\n                    ctrl(')')\n                        .recover_with(via_parser(end()))\n                        .recover_with(skip_then_retry_until(\n                            any_ref().ignored(),\n                            ctrl(')').ignored().or(end()),\n                        )),\n                )\n        };\n        let interpolation = interpolation();\n        let case = case(expr.clone());\n\n        let param = select_ref! { lr::Token { kind: TokenKind::Param(id), .. } => ExprKind::Param(id.clone()) };\n\n        let term = with_doc_comment(\n            choice((\n                literal,\n                internal,\n                tuple,\n                array,\n                interpolation,\n                ident_kind,\n                case,\n                param,\n            ))\n            .map_with(|kind, extra| ExprKind::into_expr(kind, extra.span()))\n            // No longer used given the TODO in `pipeline`; can remove if we\n            // don't resolve.\n            // .or(aliased(expr.clone()))\n            .or(pipeline_expr),\n        )\n        .boxed();\n\n        let term = unary(term);\n        let term = range(term);\n\n        // Binary operators using Pratt parsing\n        // Precedence levels (higher = tighter binding):\n        // 6: Pow (right associative)\n        // 5: Mul, Div, Mod (left associative)\n        // 4: Add, Sub (left associative)\n        // 3: Compare operators (left associative)\n        // 2: Coalesce (left associative)\n        // 1: And (left associative)\n        // 0: Or (left associative)\n        term.pratt((\n            infix(right(6), operator_pow(), |left, op, right, extra| {\n                let span = extra.span();\n                ExprKind::Binary(BinaryExpr {\n                    left: Box::new(left),\n                    op,\n                    right: Box::new(right),\n                })\n                .into_expr(span)\n            }),\n            infix(left(5), operator_mul(), |left, op, right, extra| {\n                let span = extra.span();\n                ExprKind::Binary(BinaryExpr {\n                    left: Box::new(left),\n                    op,\n                    right: Box::new(right),\n                })\n                .into_expr(span)\n            }),\n            infix(left(4), operator_add(), |left, op, right, extra| {\n                let span = extra.span();\n                ExprKind::Binary(BinaryExpr {\n                    left: Box::new(left),\n                    op,\n                    right: Box::new(right),\n                })\n                .into_expr(span)\n            }),\n            infix(left(3), operator_compare(), |left, op, right, extra| {\n                let span = extra.span();\n                ExprKind::Binary(BinaryExpr {\n                    left: Box::new(left),\n                    op,\n                    right: Box::new(right),\n                })\n                .into_expr(span)\n            }),\n            infix(left(2), operator_coalesce(), |left, op, right, extra| {\n                let span = extra.span();\n                ExprKind::Binary(BinaryExpr {\n                    left: Box::new(left),\n                    op,\n                    right: Box::new(right),\n                })\n                .into_expr(span)\n            }),\n            infix(left(1), operator_and(), |left, op, right, extra| {\n                let span = extra.span();\n                ExprKind::Binary(BinaryExpr {\n                    left: Box::new(left),\n                    op,\n                    right: Box::new(right),\n                })\n                .into_expr(span)\n            }),\n            infix(left(0), operator_or(), |left, op, right, extra| {\n                let span = extra.span();\n                ExprKind::Binary(BinaryExpr {\n                    left: Box::new(left),\n                    op,\n                    right: Box::new(right),\n                })\n                .into_expr(span)\n            }),\n        ))\n        .boxed()\n    })\n}\n\nfn tuple<'a, I>(\n    nested_expr: impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n) -> impl Parser<'a, I, ExprKind, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    use chumsky::recovery::{skip_then_retry_until, via_parser};\n\n    sequence(maybe_aliased(nested_expr))\n        .delimited_by(\n            ctrl('{'),\n            ctrl('}')\n                .recover_with(via_parser(end()))\n                .recover_with(skip_then_retry_until(\n                    any_ref().ignored(),\n                    ctrl('}').ignored().or(ctrl(',').ignored()).or(end()),\n                )),\n        )\n        .map(ExprKind::Tuple)\n        .labelled(\"tuple\")\n        .boxed()\n}\n\nfn array<'a, I>(\n    expr: impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n) -> impl Parser<'a, I, ExprKind, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    use chumsky::recovery::{skip_then_retry_until, via_parser};\n\n    sequence(expr)\n        .delimited_by(\n            ctrl('['),\n            ctrl(']')\n                .recover_with(via_parser(end()))\n                .recover_with(skip_then_retry_until(\n                    any_ref().ignored(),\n                    ctrl(']').ignored().or(ctrl(',').ignored()).or(end()),\n                )),\n        )\n        .map(ExprKind::Array)\n        .labelled(\"array\")\n        .boxed()\n}\n\nfn interpolation<'a, I>() -> impl Parser<'a, I, ExprKind, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    select_ref! {\n        lr::Token { kind: TokenKind::Interpolation('s', string), .. } => (ExprKind::SString as fn(_) -> _, string.clone()),\n        lr::Token { kind: TokenKind::Interpolation('f', string), .. } => (ExprKind::FString as fn(_) -> _, string.clone()),\n    }\n    .validate(|(finish, string), extra, emit| {\n        let span = extra.span();\n        match interpolation::parse(string, span + 2) {\n            Ok(items) => finish(items),\n            Err(errors) => {\n                for err in errors {\n                    // Convert Error to Rich for emission\n                    let err_span = err.span.unwrap_or(span);\n                    // Use the reason's Display impl, not Error's Debug\n                    let message = err.reason.to_string();\n                    emit.emit(Rich::custom(err_span, message));\n                }\n                finish(vec![])\n            }\n        }\n    })\n    .labelled(\"interpolated string\")\n}\n\nfn case<'a, I>(\n    expr: impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n) -> impl Parser<'a, I, ExprKind, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    // The `nickname != null => nickname,` part\n    let mapping = func_call(expr.clone())\n        .map(Box::new)\n        .then_ignore(select_ref! { lr::Token { kind: TokenKind::ArrowFat, .. } => () })\n        .then(func_call(expr).map(Box::new))\n        .map(|(condition, value)| SwitchCase { condition, value });\n\n    keyword(\"case\")\n        .ignore_then(sequence(mapping).delimited_by(ctrl('['), ctrl(']')))\n        .map(ExprKind::Case)\n}\n\nfn unary<'a, I, E>(expr: E) -> impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n    E: Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n{\n    expr.clone()\n        .or(operator_unary()\n            .then(expr.map(Box::new))\n            .map(|(op, expr)| ExprKind::Unary(UnaryExpr { op, expr }))\n            .map_with(|kind, extra| ExprKind::into_expr(kind, extra.span())))\n        .boxed()\n}\n\nfn range<'a, I, E>(expr: E) -> impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n    E: Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n{\n    // Ranges have five cases we need to parse:\n    // x..y (bounded)\n    // x..  (only start bound)\n    // x    (no-op)\n    //  ..y (only end bound)\n    //  ..  (unbounded)\n    #[derive(Clone)]\n    enum RangeCase {\n        NoOp(Expr),\n        Range(Option<Expr>, Option<Expr>),\n    }\n    choice((\n        // with start bound (first 3 cases)\n        expr.clone()\n            .then(choice((\n                // range and end bound\n                select_ref! { lr::Token { kind: TokenKind::Range { bind_left: true, bind_right: true }, .. } => () }\n                    .ignore_then(expr.clone())\n                    .map(|x| Some(Some(x))),\n                // range and no end bound\n                select_ref! { lr::Token { kind: TokenKind::Range { bind_left: true, .. }, .. } => Some(None) },\n                // no range\n                empty().to(None),\n            )))\n            .map(|(start, range)| {\n                if let Some(end) = range {\n                    RangeCase::Range(Some(start), end)\n                } else {\n                    RangeCase::NoOp(start)\n                }\n            }),\n        // only end bound\n        select_ref! { lr::Token { kind: TokenKind::Range { bind_right: true, .. }, .. } => () }\n            .ignore_then(expr)\n            .map(|range| RangeCase::Range(None, Some(range))),\n        // unbounded\n        select_ref! { lr::Token { kind: TokenKind::Range { .. }, .. } => RangeCase::Range(None, None) },\n    ))\n    .map_with(|case, extra| {\n        let span = extra.span();\n        match case {\n            RangeCase::NoOp(x) => x,\n            RangeCase::Range(start, end) => {\n                let kind = ExprKind::Range(Range {\n                    start: start.map(Box::new),\n                    end: end.map(Box::new),\n                });\n                kind.into_expr(span)\n            }\n        }\n    })\n    .boxed()\n}\n\n/// A pipeline of `expr`, separated by pipes. Doesn't require parentheses.\npub(crate) fn pipeline<'a, I, E>(expr: E) -> impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n    E: Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n{\n    // expr has to be a param, because it can be either a normal expr() or a\n    // recursive expr called from within expr(), which causes a stack overflow\n\n    // TODO: do we need the `maybe_aliased` here rather than in `expr`? We had\n    // tried `with_doc_comment(expr)` in #4775 (and push an aliased expr into\n    // `expr`) but couldn't get it work.\n    with_doc_comment(maybe_aliased(expr))\n        .separated_by(pipe())\n        .at_least(1)\n        .collect::<Vec<_>>()\n        .map_with(|exprs: Vec<Expr>, extra| {\n            let span = extra.span();\n            // If there's only one expr, then we don't need to wrap it\n            // in a pipeline — just return the lone expr. Otherwise,\n            // wrap them in a pipeline.\n            exprs.into_iter().exactly_one().unwrap_or_else(|exprs| {\n                ExprKind::Pipeline(Pipeline {\n                    exprs: exprs.collect(),\n                })\n                .into_expr(span)\n            })\n        })\n        .labelled(\"pipeline\")\n}\n\n// Can remove if we don't end up using this\n#[allow(dead_code)]\n#[cfg(not(coverage))]\nfn aliased<'a, I, E>(expr: E) -> impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n    E: Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n{\n    let aliased = ident_part()\n        .then_ignore(ctrl('='))\n        .then(expr)\n        .map(|(alias, mut expr)| {\n            expr.alias = Some(alias);\n            expr\n        });\n    // Because `expr` accounts for parentheses, and aliased is `x=$expr`, we\n    // need to allow another layer of parentheses here.\n    aliased\n        .clone()\n        .or(aliased.delimited_by(ctrl('('), ctrl(')')))\n}\n\nfn maybe_aliased<'a, I, E>(expr: E) -> impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n    E: Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n{\n    let aliased = ident_part()\n        .then_ignore(ctrl('='))\n        // This is added for `maybe_aliased`; possibly we should integrate\n        // the funcs\n        .or_not()\n        .then(expr)\n        .map(|(alias, mut expr)| {\n            expr.alias = alias.or(expr.alias);\n            expr\n        });\n    // Because `expr` accounts for parentheses, and aliased is `x=$expr`, we\n    // need to allow another layer of parentheses here.\n    aliased\n        .clone()\n        .or(aliased.delimited_by(ctrl('('), ctrl(')')))\n}\n\nfn func_call<'a, I, E>(expr: E) -> impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n    E: Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n{\n    let func_name = expr.clone();\n\n    let named_arg = ident_part()\n        .map(Some)\n        .then_ignore(ctrl(':'))\n        .then(expr.clone());\n\n    // TODO: I think this possibly should be restructured. Currently in the case\n    // of `derive x = 5`, the `x` is an alias of a single positional argument.\n    // That then means we incorrectly allow something like `derive x = 5 y = 6`,\n    // since there are two positional arguments each with an alias. This then\n    // leads to quite confusing error messages.\n    //\n    // Instead, we could only allow a single alias per function call as the\n    // first positional argument? (I worry that not simple though...).\n    // Alternatively we could change the language to enforce tuples, so `derive\n    // {x = 5}` were required. But we still need to account for the `join`\n    // example below, which doesn't work so well in a tuple; so I'm not sure\n    // this helps much.\n    //\n    // As a reminder, we need to account for `derive x = 5` and `join a=artists\n    // (id==album_id)`.\n    let positional_arg = maybe_aliased(expr.clone()).map(|e| (None, e));\n\n    func_name\n        .then(named_arg.or(positional_arg).repeated().collect::<Vec<_>>())\n        .validate(\n            |(name, args): (Expr, Vec<(Option<String>, Expr)>), extra, emit| {\n                let span = extra.span();\n                if args.is_empty() {\n                    return name.kind;\n                }\n\n                let mut named_args = HashMap::new();\n                let mut positional = Vec::new();\n\n                for (name, arg) in args {\n                    if let Some(name) = name {\n                        match named_args.entry(name) {\n                            Entry::Occupied(entry) => {\n                                emit.emit(Rich::custom(\n                                    span,\n                                    format!(\"argument '{}' is used multiple times\", entry.key()),\n                                ));\n                            }\n                            Entry::Vacant(entry) => {\n                                entry.insert(arg);\n                            }\n                        }\n                    } else {\n                        positional.push(arg);\n                    }\n                }\n\n                ExprKind::FuncCall(FuncCall {\n                    name: Box::new(name),\n                    args: positional,\n                    named_args,\n                })\n            },\n        )\n        .map_with(|kind, extra| ExprKind::into_expr(kind, extra.span()))\n        .labelled(\"function call\")\n        .boxed()\n}\n\nfn lambda_func<'a, I, E>(expr: E) -> impl Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n    E: Parser<'a, I, Expr, ParserError<'a>> + Clone + 'a,\n{\n    let param = ident_part()\n        .then(type_expr().delimited_by(ctrl('<'), ctrl('>')).or_not())\n        .then(ctrl(':').ignore_then(expr.clone().map(Box::new)).or_not());\n\n    choice((\n        // func\n        keyword(\"func\").ignore_then(\n            param\n                .clone()\n                .separated_by(new_line().repeated())\n                .allow_leading()\n                .allow_trailing()\n                .collect::<Vec<_>>(),\n        ),\n        // plain\n        param.repeated().collect(),\n    ))\n    .then_ignore(select_ref! { lr::Token { kind: TokenKind::ArrowThin, .. } => () })\n    // return type\n    .then(type_expr().delimited_by(ctrl('<'), ctrl('>')).or_not())\n    // body\n    .then(func_call(expr))\n    .map(|((params, return_ty), body)| {\n        let (pos, name) = params\n            .into_iter()\n            .map(|((name, ty), default_value)| FuncParam {\n                name,\n                ty,\n                default_value,\n            })\n            .partition(|p| p.default_value.is_none());\n\n        Box::new(Func {\n            params: pos,\n            named_params: name,\n\n            body: Box::new(body),\n            return_ty,\n        })\n    })\n    .map(ExprKind::Func)\n    .map_with(|kind, extra| ExprKind::into_expr(kind, extra.span()))\n    .labelled(\"function definition\")\n    .boxed()\n}\n\npub(crate) fn ident<'a, I>() -> impl Parser<'a, I, Ident, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    ident_part()\n        .then_ignore(ctrl('.'))\n        .repeated()\n        .collect()\n        .then(choice((ident_part(), ctrl('*').map(|_| \"*\".to_string()))))\n        .map(|(mut parts, last): (Vec<String>, String)| {\n            parts.push(last);\n            Ident::from_path(parts)\n        })\n}\n\nfn operator_unary<'a, I>() -> impl Parser<'a, I, UnOp, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    (ctrl('+').to(UnOp::Add))\n        .or(ctrl('-').to(UnOp::Neg))\n        .or(ctrl('!').to(UnOp::Not))\n        .or(select_ref! { lr::Token { kind: TokenKind::Eq, .. } => UnOp::EqSelf })\n}\nfn operator_pow<'a, I>() -> impl Parser<'a, I, BinOp, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    select_ref! { lr::Token { kind: TokenKind::Pow, .. } => BinOp::Pow }\n}\nfn operator_mul<'a, I>() -> impl Parser<'a, I, BinOp, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    (select_ref! { lr::Token { kind: TokenKind::DivInt, .. } => BinOp::DivInt })\n        .or(ctrl('*').to(BinOp::Mul))\n        .or(ctrl('/').to(BinOp::DivFloat))\n        .or(ctrl('%').to(BinOp::Mod))\n}\nfn operator_add<'a, I>() -> impl Parser<'a, I, BinOp, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    (ctrl('+').to(BinOp::Add)).or(ctrl('-').to(BinOp::Sub))\n}\nfn operator_compare<'a, I>() -> impl Parser<'a, I, BinOp, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    choice((\n        select_ref! { lr::Token { kind: TokenKind::Eq, .. } => BinOp::Eq },\n        select_ref! { lr::Token { kind: TokenKind::Ne, .. } => BinOp::Ne },\n        select_ref! { lr::Token { kind: TokenKind::Lte, .. } => BinOp::Lte },\n        select_ref! { lr::Token { kind: TokenKind::Gte, .. } => BinOp::Gte },\n        select_ref! { lr::Token { kind: TokenKind::RegexSearch, .. } => BinOp::RegexSearch },\n        ctrl('<').to(BinOp::Lt),\n        ctrl('>').to(BinOp::Gt),\n    ))\n}\nfn operator_and<'a, I>() -> impl Parser<'a, I, BinOp, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    select_ref! { lr::Token { kind: TokenKind::And, .. } => BinOp::And }\n}\nfn operator_or<'a, I>() -> impl Parser<'a, I, BinOp, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    select_ref! { lr::Token { kind: TokenKind::Or, .. } => BinOp::Or }\n}\nfn operator_coalesce<'a, I>() -> impl Parser<'a, I, BinOp, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    select_ref! { lr::Token { kind: TokenKind::Coalesce, .. } => BinOp::Coalesce }\n}\n\n#[cfg(test)]\nmod tests {\n    use insta::{assert_debug_snapshot, assert_yaml_snapshot};\n\n    use super::*;\n    use crate::error::Error;\n\n    fn parse_expr_call(source: &str) -> Result<Expr, Vec<Error>> {\n        crate::parse_test!(\n            source,\n            new_line()\n                .repeated()\n                .collect::<Vec<_>>()\n                .ignore_then(expr_call())\n                .then_ignore(new_line().repeated())\n                .then_ignore(end())\n        )\n    }\n\n    fn parse_tuple(source: &str) -> Result<Expr, Vec<Error>> {\n        crate::parse_test!(\n            source,\n            new_line()\n                .repeated()\n                .collect::<Vec<_>>()\n                .ignore_then(tuple(expr()))\n                .map_with(|kind, extra| ExprKind::into_expr(kind, extra.span()))\n                .then_ignore(new_line().repeated())\n                .then_ignore(end())\n        )\n    }\n\n    fn parse_any_expr(source: &str) -> Result<Expr, Vec<Error>> {\n        crate::parse_test!(\n            source,\n            new_line()\n                .repeated()\n                .collect::<Vec<_>>()\n                .ignore_then(expr())\n        )\n    }\n\n    fn parse_pipeline(source: &str) -> Result<Expr, Vec<Error>> {\n        crate::parse_test!(\n            source,\n            new_line()\n                .repeated()\n                .collect::<Vec<_>>()\n                .ignore_then(pipeline(expr_call()))\n                .then_ignore(new_line().repeated())\n                .then_ignore(end())\n        )\n    }\n\n    fn parse_case(source: &str) -> Result<Expr, Vec<Error>> {\n        crate::parse_test!(\n            source,\n            new_line()\n                .repeated()\n                .collect::<Vec<_>>()\n                .ignore_then(case(expr()))\n                .map_with(|kind, extra| ExprKind::into_expr(kind, extra.span()))\n                .then_ignore(new_line().repeated())\n                .then_ignore(end())\n        )\n    }\n\n    fn parse_expr_call_complete(source: &str) -> Result<Expr, Vec<Error>> {\n        crate::parse_test!(\n            source,\n            new_line()\n                .repeated()\n                .collect::<Vec<_>>()\n                .ignore_then(expr_call())\n                .then_ignore(end())\n        )\n    }\n\n    #[test]\n    fn test_expr_call() {\n        assert_yaml_snapshot!(\n            parse_expr_call(r#\"derive x = 5\"#).unwrap(),\n             @r#\"\n        FuncCall:\n          name:\n            Ident:\n              - derive\n            span: \"0:0-6\"\n          args:\n            - Literal:\n                Integer: 5\n              span: \"0:11-12\"\n              alias: x\n        span: \"0:0-12\"\n        \"#);\n\n        assert_yaml_snapshot!(\n            parse_expr_call(r#\"aggregate {sum salary}\"#).unwrap(),\n             @r#\"\n        FuncCall:\n          name:\n            Ident:\n              - aggregate\n            span: \"0:0-9\"\n          args:\n            - Tuple:\n                - FuncCall:\n                    name:\n                      Ident:\n                        - sum\n                      span: \"0:11-14\"\n                    args:\n                      - Ident:\n                          - salary\n                        span: \"0:15-21\"\n                  span: \"0:11-21\"\n              span: \"0:10-22\"\n        span: \"0:0-22\"\n        \"#);\n    }\n\n    // The behavior that expr() doesn't parse aliases is tested by test_tuple\n\n    #[test]\n    fn test_tuple() {\n        assert_yaml_snapshot!(\n            parse_tuple(r#\"{a = 5, b = 6}\"#).unwrap(),\n            @r#\"\n        Tuple:\n          - Literal:\n              Integer: 5\n            span: \"0:5-6\"\n            alias: a\n          - Literal:\n              Integer: 6\n            span: \"0:12-13\"\n            alias: b\n        span: \"0:0-14\"\n        \"#);\n\n        assert_debug_snapshot!(\n            parse_tuple(r#\"\n            {a = 5\n             b = 6}\"#).unwrap_err(),\n            @r#\"\n        [\n            Error {\n                kind: Error,\n                span: Some(\n                    0:33-34,\n                ),\n                reason: Expected {\n                    who: None,\n                    expected: \"new line or something else\",\n                    found: \"b\",\n                },\n                hints: [],\n                code: None,\n            },\n        ]\n        \"#);\n\n        assert_yaml_snapshot!(parse_tuple(r#\"{d_str = (d | date.to_text \"%Y/%m/%d\")}\"#).unwrap(),\n        @r#\"\n        Tuple:\n          - Pipeline:\n              exprs:\n                - Ident:\n                    - d\n                  span: \"0:10-11\"\n                - FuncCall:\n                    name:\n                      Ident:\n                        - date\n                        - to_text\n                      span: \"0:14-26\"\n                    args:\n                      - Literal:\n                          String: \"%Y/%m/%d\"\n                        span: \"0:27-37\"\n                  span: \"0:14-37\"\n            span: \"0:10-37\"\n            alias: d_str\n        span: \"0:0-39\"\n        \"#);\n    }\n\n    #[test]\n    fn test_expr() {\n        assert_yaml_snapshot!(\n            parse_any_expr(r#\"5+5\"#).unwrap(),\n             @r#\"\n        Binary:\n          left:\n            Literal:\n              Integer: 5\n            span: \"0:0-1\"\n          op: Add\n          right:\n            Literal:\n              Integer: 5\n            span: \"0:2-3\"\n        span: \"0:0-3\"\n        \"#);\n    }\n\n    #[test]\n    fn test_pipeline() {\n        assert_yaml_snapshot!(\n            parse_pipeline(r#\"\n            (\n              from artists\n              derive x = 5\n            )\n            \"#).unwrap(),\n            @r#\"\n        Pipeline:\n          exprs:\n            - FuncCall:\n                name:\n                  Ident:\n                    - from\n                  span: \"0:29-33\"\n                args:\n                  - Ident:\n                      - artists\n                    span: \"0:34-41\"\n              span: \"0:29-41\"\n            - FuncCall:\n                name:\n                  Ident:\n                    - derive\n                  span: \"0:56-62\"\n                args:\n                  - Literal:\n                      Integer: 5\n                    span: \"0:67-68\"\n                    alias: x\n              span: \"0:56-68\"\n        span: \"0:13-82\"\n        \"#);\n    }\n\n    #[test]\n    fn test_case() {\n        assert_yaml_snapshot!(\n            parse_case(r#\"\n\n        case [\n\n            nickname != null => nickname,\n            true => null\n\n        ]\n            \"#).unwrap(),\n        @r#\"\n        Case:\n          - condition:\n              Binary:\n                left:\n                  Ident:\n                    - nickname\n                  span: \"0:30-38\"\n                op: Ne\n                right:\n                  Literal: \"Null\"\n                  span: \"0:42-46\"\n              span: \"0:30-46\"\n            value:\n              Ident:\n                - nickname\n              span: \"0:50-58\"\n          - condition:\n              Literal:\n                Boolean: true\n              span: \"0:72-76\"\n            value:\n              Literal: \"Null\"\n              span: \"0:80-84\"\n        span: \"0:0-95\"\n        \"#);\n    }\n\n    // this should return an error but doesn't yet\n    #[should_panic]\n    #[test]\n    fn should_error_01() {\n        assert_debug_snapshot!(\n            parse_expr_call_complete(r#\"derive {x = y z = 3}\"#).unwrap_err(),\n            @r###\"\n        \"###);\n    }\n\n    #[test]\n    fn tuple_missing_comma() {\n        assert_debug_snapshot!(\n            parse_expr_call_complete(r#\"{\n              x = y\n              z = 3\n            }\"#).unwrap_err(),\n            @r#\"\n        [\n            Error {\n                kind: Error,\n                span: Some(\n                    0:36-37,\n                ),\n                reason: Expected {\n                    who: None,\n                    expected: \"new line or something else\",\n                    found: \"z\",\n                },\n                hints: [],\n                code: None,\n            },\n        ]\n        \"#);\n    }\n\n    #[test]\n    fn args_in_parens() {\n        // Ensure function arguments allow parentheses\n        assert_yaml_snapshot!(\n            parse_expr_call_complete(r#\"f (a) b\"#).unwrap(), @r#\"\n        FuncCall:\n          name:\n            Ident:\n              - f\n            span: \"0:0-1\"\n          args:\n            - Ident:\n                - a\n              span: \"0:3-4\"\n            - Ident:\n                - b\n              span: \"0:6-7\"\n        span: \"0:0-7\"\n        \"#);\n\n        assert_yaml_snapshot!(\n            parse_expr_call_complete(r#\"f (a=2) b\"#).unwrap(), @r#\"\n        FuncCall:\n          name:\n            Ident:\n              - f\n            span: \"0:0-1\"\n          args:\n            - Literal:\n                Integer: 2\n              span: \"0:5-6\"\n              alias: a\n            - Ident:\n                - b\n              span: \"0:8-9\"\n        span: \"0:0-9\"\n        \"#);\n\n        assert_yaml_snapshot!(\n            parse_expr_call_complete(r#\"f (a b)\"#).unwrap(), @r#\"\n        FuncCall:\n          name:\n            Ident:\n              - f\n            span: \"0:0-1\"\n          args:\n            - FuncCall:\n                name:\n                  Ident:\n                    - a\n                  span: \"0:3-4\"\n                args:\n                  - Ident:\n                      - b\n                    span: \"0:5-6\"\n              span: \"0:3-6\"\n        span: \"0:0-7\"\n        \"#);\n    }\n\n    #[test]\n    fn pipeline_starting_with_alias_expr() {\n        let source = r#\"\n    (\n      tbl\n      select t.date\n    )\n    \"#;\n\n        assert_yaml_snapshot!(parse_pipeline(source).unwrap(), @r#\"\n        Pipeline:\n          exprs:\n            - Ident:\n                - tbl\n              span: \"0:13-16\"\n            - FuncCall:\n                name:\n                  Ident:\n                    - select\n                  span: \"0:23-29\"\n                args:\n                  - Ident:\n                      - t\n                      - date\n                    span: \"0:30-36\"\n              span: \"0:23-36\"\n        span: \"0:5-42\"\n        \"#);\n\n        let source = r#\"\n    (\n      t = tbl\n      select t.date\n    )\n    \"#;\n\n        assert_yaml_snapshot!(parse_pipeline(source).unwrap(), @r#\"\n        Pipeline:\n          exprs:\n            - Ident:\n                - tbl\n              span: \"0:17-20\"\n              alias: t\n            - FuncCall:\n                name:\n                  Ident:\n                    - select\n                  span: \"0:27-33\"\n                args:\n                  - Ident:\n                      - t\n                      - date\n                    span: \"0:34-40\"\n              span: \"0:27-40\"\n        span: \"0:5-46\"\n        \"#);\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/interpolation.rs",
    "content": "use chumsky::input::{SliceInput, ValueInput};\nuse chumsky::prelude::*;\n\nuse crate::error::{Error, WithErrorInfo};\nuse crate::parser::pr::*;\nuse crate::span::Span;\n\n/// Parses interpolated strings\npub(crate) fn parse(string: String, span_base: Span) -> Result<Vec<InterpolateItem>, Vec<Error>> {\n    let res = interpolated_parser().parse(string.as_str());\n\n    let (output, errors) = res.into_output_errors();\n\n    if !errors.is_empty() {\n        return Err(errors\n            .into_iter()\n            .map(|e| {\n                // Adjust span to be relative to span_base\n                let span = Span {\n                    start: span_base.start + e.span().start,\n                    end: span_base.start + e.span().end,\n                    source_id: span_base.source_id,\n                };\n\n                // Convert Rich error to our Error format\n                // Custom error formatting for consistent user experience across all PRQL errors.\n                // Chumsky's default format varies between versions and doesn't match our\n                // \"{label} expected {X}, but found {Y}\" pattern used elsewhere.\n                let message = {\n                    // Get the label from contexts (most specific one)\n                    let label = e.contexts().last().map(|(pat, _)| pat.to_string());\n\n                    // Build expected list\n                    let expected: Vec<_> = e.expected().map(|e| format!(\"{e}\")).collect();\n                    let expected_str = match expected.len() {\n                        0 => String::new(),\n                        1 => expected[0].clone(),\n                        2 => format!(\"{} or {}\", expected[0], expected[1]),\n                        _ => format!(\n                            \"{}, or {}\",\n                            expected[..expected.len() - 1].join(\", \"),\n                            expected.last().unwrap()\n                        ),\n                    };\n\n                    // Format the found token consistently: quote actual tokens, but not \"end of input\"\n                    let found = if let Some(f) = e.found() {\n                        format!(\"\\\"{}\\\"\", f)\n                    } else {\n                        \"end of input\".to_string()\n                    };\n\n                    if let Some(label) = label {\n                        if expected_str.is_empty() {\n                            format!(\"unexpected {found}\")\n                        } else {\n                            format!(\"{label} expected {expected_str}, but found {found}\")\n                        }\n                    } else if expected_str.is_empty() {\n                        format!(\"unexpected {found}\")\n                    } else {\n                        format!(\"expected {expected_str}, but found {found}\")\n                    }\n                };\n\n                WithErrorInfo::with_span(Error::new_simple(message), Some(span))\n            })\n            .collect());\n    }\n\n    // Adjust spans in the output to be relative to span_base\n    let adjusted_output = output\n        .unwrap_or_default()\n        .into_iter()\n        .map(|item| match item {\n            InterpolateItem::Expr { expr, format } => {\n                let adjusted_expr = Box::new(Expr {\n                    span: expr.span.map(|s| Span {\n                        start: span_base.start + s.start,\n                        end: span_base.start + s.end,\n                        source_id: span_base.source_id,\n                    }),\n                    ..(*expr)\n                });\n                InterpolateItem::Expr {\n                    expr: adjusted_expr,\n                    format,\n                }\n            }\n            InterpolateItem::String(s) => InterpolateItem::String(s),\n        })\n        .collect();\n\n    Ok(adjusted_output)\n}\n\nfn interpolated_parser<'a, I>(\n) -> impl Parser<'a, I, Vec<InterpolateItem>, extra::Err<Rich<'a, char, SimpleSpan>>>\nwhere\n    I: ValueInput<'a, Token = char, Span = SimpleSpan> + SliceInput<'a, Slice = &'a str>,\n{\n    let expr = interpolate_ident_part()\n        .separated_by(just('.'))\n        .at_least(1)\n        .collect()\n        .map(Ident::from_path)\n        .map(ExprKind::Ident)\n        .map_with(|kind, extra| {\n            // Convert SimpleSpan to our Span type (will be adjusted in parse() function)\n            let simple_span: SimpleSpan = extra.span();\n            let span = Span {\n                start: simple_span.start,\n                end: simple_span.end,\n                source_id: 0,\n            };\n            ExprKind::into_expr(kind, span)\n        })\n        .map(Box::new)\n        .labelled(\"interpolated string variable\")\n        .then(\n            just(':')\n                .ignore_then(none_of('}').repeated().collect::<String>())\n                .or_not(),\n        )\n        .delimited_by(just('{'), just('}'))\n        .map(|(expr, format)| InterpolateItem::Expr { expr, format });\n\n    // Convert double braces to single braces, and fail on any single braces.\n    let string = just(\"{{\")\n        .to('{')\n        .or(just(\"}}\").to('}'))\n        .or(none_of(\"{}\"))\n        .repeated()\n        .at_least(1)\n        .collect::<String>()\n        .map(InterpolateItem::String);\n\n    expr.or(string).repeated().collect().then_ignore(end())\n}\n\npub(crate) fn interpolate_ident_part<'a, I>(\n) -> impl Parser<'a, I, String, extra::Err<Rich<'a, char, SimpleSpan>>> + Clone\nwhere\n    I: ValueInput<'a, Token = char, Span = SimpleSpan> + SliceInput<'a, Slice = &'a str>,\n{\n    let plain = any()\n        .filter(|c: &char| c.is_alphabetic() || *c == '_')\n        .then(\n            any()\n                .filter(|c: &char| c.is_alphanumeric() || *c == '_')\n                .repeated(),\n        )\n        .to_slice()\n        .map(|s: &str| s.to_string())\n        .labelled(\"interpolated string\");\n\n    let backticks = none_of('`')\n        .repeated()\n        .to_slice()\n        .map(|s: &str| s.to_string())\n        .delimited_by(just('`'), just('`'));\n\n    plain.or(backticks.labelled(\"interp:backticks\"))\n}\n\n#[test]\nfn parse_interpolate() {\n    use insta::assert_debug_snapshot;\n    let span_base = Span::new(0, 0..0);\n\n    assert_debug_snapshot!(\n        parse(\"concat({a})\".to_string(), span_base).unwrap(),\n    @r#\"\n    [\n        String(\n            \"concat(\",\n        ),\n        Expr {\n            expr: Expr {\n                kind: Ident(\n                    [\n                        \"a\",\n                    ],\n                ),\n                span: Some(\n                    0:8-9,\n                ),\n                alias: None,\n                doc_comment: None,\n            },\n            format: None,\n        },\n        String(\n            \")\",\n        ),\n    ]\n    \"#);\n\n    assert_debug_snapshot!(\n        parse(\"print('{{hello}}')\".to_string(), span_base).unwrap(),\n    @r#\"\n    [\n        String(\n            \"print('{hello}')\",\n        ),\n    ]\n    \"#);\n\n    assert_debug_snapshot!(\n        parse(\"concat('{{', a, '}}')\".to_string(), span_base).unwrap(),\n    @r#\"\n    [\n        String(\n            \"concat('{', a, '}')\",\n        ),\n    ]\n    \"#);\n\n    assert_debug_snapshot!(\n        parse(\"concat('{{', {a}, '}}')\".to_string(), span_base).unwrap(),\n    @r#\"\n    [\n        String(\n            \"concat('{', \",\n        ),\n        Expr {\n            expr: Expr {\n                kind: Ident(\n                    [\n                        \"a\",\n                    ],\n                ),\n                span: Some(\n                    0:14-15,\n                ),\n                alias: None,\n                doc_comment: None,\n            },\n            format: None,\n        },\n        String(\n            \", '}')\",\n        ),\n    ]\n    \"#);\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/mod.rs",
    "content": "use chumsky;\nuse chumsky::input::BorrowInput;\nuse chumsky::prelude::*;\nuse chumsky::span::SimpleSpan;\n\nuse self::pr::{Annotation, Stmt, StmtKind};\nuse crate::error::Error;\nuse crate::lexer::lr;\nuse crate::lexer::lr::TokenKind;\nuse crate::span::Span;\n\n// Type alias for parser error type to reduce verbosity\npub(crate) type ParserError<'a> = extra::Err<Rich<'a, lr::Token, Span>>;\n\nmod expr;\nmod interpolation;\npub(crate) mod perror;\npub mod pr;\npub(crate) mod stmt;\n#[cfg(test)]\nmod test;\nmod types;\n\n// Note that `parse_source` is in `prqlc` crate, not in `prqlc-parser` crate,\n// because it logs using the logging framework in `prqlc`.\n\npub fn parse_lr_to_pr(source_id: u16, lr: Vec<lr::Token>) -> (Option<Vec<pr::Stmt>>, Vec<Error>) {\n    // Filter out comments - we don't want them in the AST\n    let semantic_tokens: Vec<_> = lr\n        .into_iter()\n        .filter(|token| {\n            !matches!(\n                token.kind,\n                lr::TokenKind::Comment(_) | lr::TokenKind::LineWrap(_)\n            )\n        })\n        .collect();\n\n    // Use built-in Input impl for &[Token], then map_span to convert token indices to byte spans\n    let input = semantic_tokens\n        .as_slice()\n        .map_span(|simple_span: SimpleSpan| {\n            let start_idx = simple_span.start();\n            let end_idx = simple_span.end();\n\n            // Convert token indices to byte offsets in the source file\n            let start = semantic_tokens\n                .get(start_idx)\n                .map(|t| t.span.start)\n                .unwrap_or(0);\n            let end = semantic_tokens\n                .get(end_idx.saturating_sub(1))\n                .map(|t| t.span.end)\n                .unwrap_or(start);\n\n            Span {\n                start,\n                end,\n                source_id,\n            }\n        });\n\n    let parse_result = stmt::source().parse(input);\n    let (pr, parse_errors) = parse_result.into_output_errors();\n\n    let errors = parse_errors.into_iter().map(|e| e.into()).collect();\n    log::debug!(\"parse errors: {errors:?}\");\n\n    (pr, errors)\n}\n\nfn ident_part<'a, I>() -> impl Parser<'a, I, String, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    select_ref! {\n        lr::Token { kind: TokenKind::Ident(ident), .. } => ident.clone(),\n    }\n}\n\nfn keyword<'a, I>(kw: &'static str) -> impl Parser<'a, I, (), ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    select_ref! {\n        lr::Token { kind: TokenKind::Keyword(k), .. } if k == kw => (),\n    }\n}\n\n/// Our approach to new lines is each item consumes new lines _before_ itself,\n/// but not newlines after itself. This allows us to enforce new lines between\n/// some items. The only place we handle new lines after an item is in the root\n/// parser.\npub(crate) fn new_line<'a, I>() -> impl Parser<'a, I, (), ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    select_ref! {\n        lr::Token { kind: TokenKind::NewLine, .. } => (),\n        lr::Token { kind: TokenKind::Start, .. } => (),\n    }\n    .labelled(\"new line\")\n}\n\nfn ctrl<'a, I>(char: char) -> impl Parser<'a, I, (), ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    select_ref! {\n        lr::Token { kind: TokenKind::Control(c), .. } if *c == char => (),\n    }\n}\n\nfn into_stmt((annotations, kind): (Vec<Annotation>, StmtKind), span: Span) -> Stmt {\n    Stmt {\n        kind,\n        span: Some(span),\n        annotations,\n        doc_comment: None,\n    }\n}\n\nfn doc_comment<'a, I>() -> impl Parser<'a, I, String, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    // doc comments must start on a new line, so we enforce a new line (which\n    // can also be a file start) before the doc comment\n    //\n    // TODO: we currently lose any empty newlines between doc comments;\n    // eventually we want to retain or restrict them\n    (new_line().repeated().at_least(1).ignore_then(select_ref! {\n        lr::Token { kind: TokenKind::DocComment(dc), .. } => dc.clone(),\n    }))\n    .repeated()\n    .at_least(1)\n    .collect()\n    .map(|lines: Vec<String>| lines.join(\"\\n\"))\n    .labelled(\"doc comment\")\n}\n\nfn with_doc_comment<'a, I, P, O>(parser: P) -> impl Parser<'a, I, O, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n    P: Parser<'a, I, O, ParserError<'a>> + Clone + 'a,\n    O: SupportsDocComment + 'a,\n{\n    doc_comment()\n        .or_not()\n        .then(parser)\n        .map(|(doc_comment, inner)| inner.with_doc_comment(doc_comment))\n}\n\n/// Allows us to surround a parser by `with_doc_comment` and for a doc comment\n/// to be added to the result, as long as the result implements `SupportsDocComment`.\n///\n/// (In retrospect, we could manage without it, though probably not worth the\n/// effort to remove it. We could also use it to also support Span items.)\ntrait SupportsDocComment {\n    fn with_doc_comment(self, doc_comment: Option<String>) -> Self;\n}\n\n/// Parse a sequence, allowing commas and new lines between items. Doesn't\n/// include the surrounding delimiters.\nfn sequence<'a, I, P, O>(parser: P) -> impl Parser<'a, I, Vec<O>, ParserError<'a>> + Clone + 'a\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n    P: Parser<'a, I, O, ParserError<'a>> + Clone + 'a,\n    O: 'a,\n{\n    parser\n        .separated_by(ctrl(',').then_ignore(new_line().repeated()))\n        .allow_trailing()\n        .collect()\n        // Note because we pad rather than only take the ending new line, we\n        // can't put items that require a new line in a tuple, like:\n        //\n        // ```\n        // {\n        //   !# doc comment\n        //   a,\n        // }\n        // ```\n        // ...but I'm not sure there's a way around it, since we do need to\n        // consume newlines in tuples...\n        .padded_by(new_line().repeated())\n}\n\nfn pipe<'a, I>() -> impl Parser<'a, I, (), ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    ctrl('|')\n        .ignored()\n        .or(new_line().repeated().at_least(1).ignored())\n}\n\n#[cfg(test)]\nmod tests {\n    use insta::assert_debug_snapshot;\n\n    use super::*;\n    use crate::error::Error;\n\n    fn parse_doc_comment(source: &str) -> Result<String, Vec<Error>> {\n        let tokens = crate::lexer::lex_source(source)?;\n        let semantic_tokens: Vec<_> = tokens\n            .0\n            .into_iter()\n            .filter(|token| {\n                !matches!(\n                    token.kind,\n                    crate::lexer::lr::TokenKind::Comment(_)\n                        | crate::lexer::lr::TokenKind::LineWrap(_)\n                )\n            })\n            .collect();\n\n        let input = semantic_tokens\n            .as_slice()\n            .map_span(|simple_span: SimpleSpan| {\n                let start_idx = simple_span.start();\n                let end_idx = simple_span.end();\n\n                let start = semantic_tokens\n                    .get(start_idx)\n                    .map(|t| t.span.start)\n                    .unwrap_or(0);\n                let end = semantic_tokens\n                    .get(end_idx.saturating_sub(1))\n                    .map(|t| t.span.end)\n                    .unwrap_or(start);\n\n                Span {\n                    start,\n                    end,\n                    source_id: 0,\n                }\n            });\n\n        let parser = doc_comment()\n            .then_ignore(new_line().repeated())\n            .then_ignore(end());\n        let (ast, errors) = parser.parse(input).into_output_errors();\n\n        if !errors.is_empty() {\n            return Err(errors.into_iter().map(Into::into).collect());\n        }\n        Ok(ast.unwrap())\n    }\n\n    #[test]\n    fn test_doc_comment() {\n        assert_debug_snapshot!(parse_doc_comment(r#\"\n        #! doc comment\n        #! another line\n\n        \"#), @r#\"\n        Ok(\n            \" doc comment\\n another line\",\n        )\n        \"#);\n    }\n\n    // Doc comment functionality is tested in stmt.rs tests\n\n    #[cfg(test)]\n    impl SupportsDocComment for String {\n        fn with_doc_comment(self, _doc_comment: Option<String>) -> Self {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/perror.rs",
    "content": "use chumsky;\nuse chumsky::error::Rich;\n\nuse crate::error::WithErrorInfo;\nuse crate::error::{Error, Reason};\nuse crate::lexer::lr::TokenKind;\nuse crate::span::Span;\n\n// Helper function to convert Rich errors to our Error type\nfn rich_error_to_error<T>(\n    span: Span,\n    reason: &chumsky::error::RichReason<T>,\n    token_to_string: impl Fn(&T) -> String,\n    is_whitespace_token: impl Fn(&T) -> bool,\n) -> Error\nwhere\n    T: std::fmt::Debug,\n{\n    use chumsky::error::RichReason;\n\n    let error = match reason {\n        RichReason::ExpectedFound { expected, found } => {\n            use chumsky::error::RichPattern;\n            let expected_strs: Vec<String> = expected\n                .iter()\n                .filter(|p| {\n                    // Filter out whitespace tokens unless that's all we're expecting\n                    let is_whitespace = match p {\n                        RichPattern::EndOfInput => true,\n                        RichPattern::Token(t) => is_whitespace_token(t),\n                        _ => false,\n                    };\n                    !is_whitespace\n                        || expected.iter().all(|p| match p {\n                            RichPattern::EndOfInput => true,\n                            RichPattern::Token(t) => is_whitespace_token(t),\n                            _ => false,\n                        })\n                })\n                .map(|p| match p {\n                    RichPattern::Token(t) => token_to_string(t),\n                    RichPattern::EndOfInput => \"end of input\".to_string(),\n                    _ => format!(\"{:?}\", p),\n                })\n                .collect();\n\n            let found_str = match found {\n                Some(t) => token_to_string(t),\n                None => \"end of input\".to_string(),\n            };\n\n            if expected_strs.is_empty() || expected_strs.len() > 10 {\n                Error::new_simple(format!(\"unexpected {found_str}\"))\n            } else {\n                let mut expected_strs = expected_strs;\n                expected_strs.sort();\n\n                let expected_str = match expected_strs.len() {\n                    1 => expected_strs[0].clone(),\n                    2 => expected_strs.join(\" or \"),\n                    _ => {\n                        let last = expected_strs.pop().unwrap();\n                        format!(\"one of {} or {last}\", expected_strs.join(\", \"))\n                    }\n                };\n\n                match found {\n                    Some(_) => Error::new(Reason::Expected {\n                        who: None,\n                        expected: expected_str,\n                        found: found_str,\n                    }),\n                    None => Error::new(Reason::Simple(format!(\n                        \"Expected {expected_str}, but didn't find anything before the end.\"\n                    ))),\n                }\n            }\n        }\n        RichReason::Custom(msg) => Error::new_simple(msg.to_string()),\n    };\n\n    error.with_span(Some(span))\n}\n\nimpl<'a> From<Rich<'a, crate::lexer::lr::Token, Span>> for Error {\n    fn from(rich: Rich<'a, crate::lexer::lr::Token, Span>) -> Error {\n        rich_error_to_error(\n            *rich.span(),\n            rich.reason(),\n            |token| format!(\"{}\", token.kind),\n            |token| matches!(token.kind, TokenKind::NewLine | TokenKind::Start),\n        )\n    }\n}\n\nimpl<'a> From<Rich<'a, TokenKind, Span>> for Error {\n    fn from(rich: Rich<'a, TokenKind, Span>) -> Error {\n        rich_error_to_error(\n            *rich.span(),\n            rich.reason(),\n            |kind| format!(\"{}\", kind),\n            |kind| matches!(kind, TokenKind::NewLine | TokenKind::Start),\n        )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use insta::{assert_debug_snapshot, assert_snapshot};\n\n    use crate::error::{Error, WithErrorInfo};\n\n    // Helper function to create a simple Error object\n    fn simple_error(message: &str) -> Error {\n        Error::new_simple(message)\n    }\n\n    #[test]\n    fn test_error_messages() {\n        let error1 = simple_error(\"test error\");\n        assert_snapshot!(error1.to_string(), @r#\"Error { kind: Error, span: None, reason: Simple(\"test error\"), hints: [], code: None }\"#);\n\n        let error2 = simple_error(\"another error\").with_span(Some(crate::span::Span {\n            start: 0,\n            end: 5,\n            source_id: 0,\n        }));\n        assert_debug_snapshot!(error2, @r#\"\n        Error {\n            kind: Error,\n            span: Some(\n                0:0-5,\n            ),\n            reason: Simple(\n                \"another error\",\n            ),\n            hints: [],\n            code: None,\n        }\n        \"#);\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/pr/expr.rs",
    "content": "use std::collections::HashMap;\n\nuse enum_as_inner::EnumAsInner;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\nuse crate::lexer::lr::Literal;\nuse crate::parser::pr::ops::{BinOp, UnOp};\nuse crate::parser::pr::{Ident, Ty};\nuse crate::span::Span;\nuse crate::{generic, parser::SupportsDocComment};\n\nimpl Expr {\n    pub fn new<K: Into<ExprKind>>(kind: K) -> Self {\n        Expr {\n            kind: kind.into(),\n            span: None,\n            alias: None,\n            doc_comment: None,\n        }\n    }\n}\n\n// The following code is tested by the tests_misc crate to match expr.rs in prqlc.\n\n/// Expr is anything that has a value and thus a type.\n/// Most of these can contain other [Expr] themselves; literals should be [ExprKind::Literal].\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct Expr {\n    #[serde(flatten)]\n    pub kind: ExprKind,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub span: Option<Span>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub alias: Option<String>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub doc_comment: Option<String>,\n}\n\nimpl SupportsDocComment for Expr {\n    fn with_doc_comment(self, doc_comment: Option<String>) -> Self {\n        Self {\n            doc_comment,\n            ..self\n        }\n    }\n}\n\n#[derive(\n    Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize, strum::AsRefStr, JsonSchema,\n)]\npub enum ExprKind {\n    Ident(Ident),\n\n    #[cfg_attr(\n        feature = \"serde_yaml\",\n        serde(with = \"serde_yaml::with::singleton_map\"),\n        schemars(with = \"Literal\")\n    )]\n    Literal(Literal),\n    Pipeline(Pipeline),\n\n    Tuple(Vec<Expr>),\n    Array(Vec<Expr>),\n    Range(Range),\n    Binary(BinaryExpr),\n    Unary(UnaryExpr),\n    FuncCall(FuncCall),\n    Func(Box<Func>),\n    SString(Vec<InterpolateItem>),\n    FString(Vec<InterpolateItem>),\n    Case(Vec<SwitchCase>),\n\n    /// placeholder for values provided after query is compiled\n    Param(String),\n\n    /// When used instead of function body, the function will be translated to a RQ operator.\n    /// Contains ident of the RQ operator.\n    Internal(String),\n}\n\nimpl ExprKind {\n    pub fn into_expr(self, span: Span) -> Expr {\n        Expr {\n            span: Some(span),\n            kind: self,\n            alias: None,\n            doc_comment: None,\n        }\n    }\n}\n\n#[derive(Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub enum IndirectionKind {\n    Name(String),\n    Position(i64),\n    Star,\n}\n\n/// Expression with two operands and an operator, such as `1 + 2`.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct BinaryExpr {\n    pub left: Box<Expr>,\n    pub op: BinOp,\n    pub right: Box<Expr>,\n}\n\n/// Expression with one operand and an operator, such as `-1`.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct UnaryExpr {\n    pub op: UnOp,\n    pub expr: Box<Expr>,\n}\n\n/// Function call.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct FuncCall {\n    pub name: Box<Expr>,\n    pub args: Vec<Expr>,\n    #[serde(default, skip_serializing_if = \"HashMap::is_empty\")]\n    pub named_args: HashMap<String, Expr>,\n}\n\n/// Function called with possibly missing positional arguments.\n/// May also contain environment that is needed to evaluate the body.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct Func {\n    /// Type requirement for the function body expression.\n    pub return_ty: Option<Ty>,\n\n    /// Expression containing parameter (and environment) references.\n    pub body: Box<Expr>,\n\n    /// Positional function parameters.\n    pub params: Vec<FuncParam>,\n\n    /// Named function parameters.\n    pub named_params: Vec<FuncParam>,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct FuncParam {\n    pub name: String,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub ty: Option<Ty>,\n\n    pub default_value: Option<Box<Expr>>,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct GenericTypeParam {\n    /// Assigned name of this generic type argument.\n    pub name: String,\n\n    pub domain: Vec<Ty>,\n}\n\n/// A value and a series of functions that are to be applied to that value one after another.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct Pipeline {\n    pub exprs: Vec<Expr>,\n}\n\npub type Range = generic::Range<Box<Expr>>;\npub type InterpolateItem = generic::InterpolateItem<Expr>;\npub type SwitchCase = generic::SwitchCase<Box<Expr>>;\n\nimpl From<Literal> for ExprKind {\n    fn from(value: Literal) -> Self {\n        ExprKind::Literal(value)\n    }\n}\n\nimpl From<Func> for ExprKind {\n    fn from(value: Func) -> Self {\n        ExprKind::Func(Box::new(value))\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/pr/ident.rs",
    "content": "use std::fmt::Write;\n\nuse schemars::JsonSchema;\nuse serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};\n\n/// A name. Generally columns, tables, functions, variables.\n/// This is glorified way of writing a \"vec with at least one element\".\n#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, JsonSchema)]\npub struct Ident {\n    pub path: Vec<String>,\n    pub name: String,\n}\n\nimpl Ident {\n    pub fn from_name<S: ToString>(name: S) -> Self {\n        Ident {\n            path: Vec::new(),\n            name: name.to_string(),\n        }\n    }\n\n    /// Creates a new ident from a non-empty path.\n    ///\n    /// Panics if path is empty.\n    pub fn from_path<S: ToString>(mut path: Vec<S>) -> Self {\n        let name = path.pop().unwrap().to_string();\n        Ident {\n            path: path.into_iter().map(|x| x.to_string()).collect(),\n            name,\n        }\n    }\n\n    pub fn len(&self) -> usize {\n        self.path.len() + 1\n    }\n\n    pub fn is_empty(&self) -> bool {\n        false\n    }\n\n    /// Remove last part of the ident.\n    /// Result will generally refer to the parent of this ident.\n    pub fn pop(self) -> Option<Self> {\n        let mut path = self.path;\n        path.pop().map(|name| Ident { path, name })\n    }\n\n    pub fn pop_front(mut self) -> (String, Option<Ident>) {\n        if self.path.is_empty() {\n            (self.name, None)\n        } else {\n            let first = self.path.remove(0);\n            (first, Some(self))\n        }\n    }\n\n    pub fn prepend(self, mut parts: Vec<String>) -> Ident {\n        parts.extend(self);\n        Ident::from_path(parts)\n    }\n\n    pub fn push(&mut self, name: String) {\n        self.path.push(std::mem::take(&mut self.name));\n        self.name = name;\n    }\n\n    pub fn with_name<S: ToString>(mut self, name: S) -> Self {\n        self.name = name.to_string();\n        self\n    }\n\n    pub fn iter(&self) -> impl Iterator<Item = &String> {\n        self.path.iter().chain(std::iter::once(&self.name))\n    }\n\n    pub fn starts_with(&self, prefix: &Ident) -> bool {\n        if prefix.len() > self.len() {\n            return false;\n        }\n        prefix\n            .iter()\n            .zip(self.iter())\n            .all(|(prefix_component, self_component)| prefix_component == self_component)\n    }\n\n    pub fn starts_with_path<S: AsRef<str>>(&self, prefix: &[S]) -> bool {\n        // self is an I\n        if prefix.len() > self.len() {\n            return false;\n        }\n        prefix\n            .iter()\n            .zip(self.iter())\n            .all(|(prefix_component, self_component)| prefix_component.as_ref() == self_component)\n    }\n\n    pub fn starts_with_part(&self, prefix: &str) -> bool {\n        self.starts_with_path(&[prefix])\n    }\n}\n\nimpl std::fmt::Debug for Ident {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_list()\n            .entries(&self.path)\n            .entry(&self.name)\n            .finish()\n    }\n}\n\nimpl std::fmt::Display for Ident {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        display_ident(f, self)\n    }\n}\n\nimpl IntoIterator for Ident {\n    type Item = String;\n    type IntoIter = std::iter::Chain<\n        std::vec::IntoIter<std::string::String>,\n        std::option::IntoIter<std::string::String>,\n    >;\n\n    fn into_iter(self) -> Self::IntoIter {\n        self.path.into_iter().chain(Some(self.name))\n    }\n}\n\nimpl std::ops::Add<Ident> for Ident {\n    type Output = Ident;\n\n    fn add(self, rhs: Ident) -> Self::Output {\n        Ident {\n            path: self.into_iter().chain(rhs.path).collect(),\n            name: rhs.name,\n        }\n    }\n}\n\nimpl Serialize for Ident {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut seq = serializer.serialize_seq(Some(self.len()))?;\n        for part in &self.path {\n            seq.serialize_element(part)?;\n        }\n        seq.serialize_element(&self.name)?;\n        seq.end()\n    }\n}\n\nimpl<'de> Deserialize<'de> for Ident {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        <Vec<String> as Deserialize>::deserialize(deserializer).map(Ident::from_path)\n    }\n}\n\npub fn display_ident(f: &mut std::fmt::Formatter, ident: &Ident) -> Result<(), std::fmt::Error> {\n    let path = &ident.path[..];\n\n    for part in path {\n        display_ident_part(f, part)?;\n        f.write_char('.')?;\n    }\n    display_ident_part(f, &ident.name)?;\n    Ok(())\n}\n\npub fn display_ident_part(f: &mut std::fmt::Formatter, s: &str) -> Result<(), std::fmt::Error> {\n    fn forbidden_start(c: char) -> bool {\n        !(c.is_ascii_alphabetic() || matches!(c, '_' | '$'))\n    }\n    fn forbidden_subsequent(c: char) -> bool {\n        !(c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '_')\n    }\n    let needs_escape = s.is_empty()\n        || s.starts_with(forbidden_start)\n        || (s.len() > 1 && s.chars().skip(1).any(forbidden_subsequent));\n\n    if needs_escape {\n        write!(f, \"`{s}`\")\n    } else {\n        write!(f, \"{s}\")\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/pr/mod.rs",
    "content": "//! PR, or \"Parser Representation\" is an AST representation of parsed PRQL. It\n//! takes LR tokens and converts them into a more structured form which\n//! understands expressions, such as tuples & functions.\n\npub use expr::*;\npub use ident::*;\npub use ops::*;\npub use stmt::*;\npub use types::*;\n\n// re-export Literal from LR, since it's encapsulated in TyKind\npub use crate::lexer::lr::Literal;\npub use crate::span::Span;\n\nmod expr;\nmod ident;\nmod ops;\nmod stmt;\nmod types;\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/pr/ops.rs",
    "content": "use schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n#[derive(\n    Debug,\n    PartialEq,\n    Eq,\n    Clone,\n    Copy,\n    Hash,\n    Serialize,\n    Deserialize,\n    strum::Display,\n    strum::EnumString,\n    JsonSchema,\n)]\npub enum UnOp {\n    #[strum(to_string = \"-\")]\n    Neg,\n    #[strum(to_string = \"+\")]\n    Add, // TODO: rename to Pos\n    #[strum(to_string = \"!\")]\n    Not,\n    #[strum(to_string = \"==\")]\n    EqSelf,\n}\n\n#[derive(\n    Debug,\n    PartialEq,\n    Eq,\n    Clone,\n    Copy,\n    Hash,\n    Serialize,\n    Deserialize,\n    strum::Display,\n    strum::EnumString,\n    JsonSchema,\n)]\npub enum BinOp {\n    #[strum(to_string = \"*\")]\n    Mul,\n    #[strum(to_string = \"//\")]\n    DivInt,\n    #[strum(to_string = \"/\")]\n    DivFloat,\n    #[strum(to_string = \"%\")]\n    Mod,\n    #[strum(to_string = \"**\")]\n    Pow,\n    #[strum(to_string = \"+\")]\n    Add,\n    #[strum(to_string = \"-\")]\n    Sub,\n    #[strum(to_string = \"==\")]\n    Eq,\n    #[strum(to_string = \"!=\")]\n    Ne,\n    #[strum(to_string = \">\")]\n    Gt,\n    #[strum(to_string = \"<\")]\n    Lt,\n    #[strum(to_string = \">=\")]\n    Gte,\n    #[strum(to_string = \"<=\")]\n    Lte,\n    #[strum(to_string = \"~=\")]\n    RegexSearch,\n    #[strum(to_string = \"&&\")]\n    And,\n    #[strum(to_string = \"||\")]\n    Or,\n    #[strum(to_string = \"??\")]\n    Coalesce,\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/pr/stmt.rs",
    "content": "use std::collections::HashMap;\n\nuse enum_as_inner::EnumAsInner;\nuse schemars::JsonSchema;\nuse semver::VersionReq;\nuse serde::{Deserialize, Serialize};\n\nuse crate::parser::pr::ident::Ident;\nuse crate::parser::pr::{Expr, Ty};\nuse crate::parser::SupportsDocComment;\nuse crate::span::Span;\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default, JsonSchema)]\npub struct QueryDef {\n    #[schemars(with = \"String\")]\n    pub version: Option<VersionReq>,\n    #[serde(default)]\n    pub other: HashMap<String, String>,\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]\npub enum VarDefKind {\n    Let,\n    Into,\n    Main,\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct Stmt {\n    #[serde(flatten)]\n    pub kind: StmtKind,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub span: Option<Span>,\n\n    #[serde(skip_serializing_if = \"Vec::is_empty\", default)]\n    pub annotations: Vec<Annotation>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub doc_comment: Option<String>,\n}\n\nimpl SupportsDocComment for Stmt {\n    fn with_doc_comment(self, doc_comment: Option<String>) -> Self {\n        Stmt {\n            doc_comment,\n            ..self\n        }\n    }\n}\n\n#[derive(Debug, EnumAsInner, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub enum StmtKind {\n    QueryDef(Box<QueryDef>),\n    VarDef(VarDef),\n    TypeDef(TypeDef),\n    ModuleDef(ModuleDef),\n    ImportDef(ImportDef),\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct VarDef {\n    pub kind: VarDefKind,\n    pub name: String,\n    pub value: Option<Box<Expr>>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub ty: Option<Ty>,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct TypeDef {\n    pub name: String,\n    pub value: Ty,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct ModuleDef {\n    pub name: String,\n    pub stmts: Vec<Stmt>,\n}\n\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]\npub struct ImportDef {\n    pub alias: Option<String>,\n    pub name: Ident,\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct Annotation {\n    pub expr: Box<Expr>,\n}\n\nimpl Stmt {\n    pub fn new(kind: StmtKind) -> Stmt {\n        Stmt {\n            kind,\n            span: None,\n            annotations: Vec::new(),\n            doc_comment: None,\n        }\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/pr/types.rs",
    "content": "use enum_as_inner::EnumAsInner;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse strum::AsRefStr;\n\nuse crate::parser::pr::ident::Ident;\nuse crate::span::Span;\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct Ty {\n    pub kind: TyKind,\n\n    pub span: Option<Span>,\n\n    /// Name inferred from the type declaration.\n    pub name: Option<String>,\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner, AsRefStr, JsonSchema)]\npub enum TyKind {\n    /// Identifier that still needs to be resolved.\n    Ident(Ident),\n\n    /// Type of a built-in primitive type\n    Primitive(PrimitiveSet),\n\n    /// Type of tuples (product)\n    Tuple(Vec<TyTupleField>),\n\n    /// Type of arrays\n    Array(Option<Box<Ty>>),\n\n    /// Type of functions with defined params and return types.\n    Function(Option<TyFunc>),\n}\n\nimpl TyKind {\n    pub fn into_ty(self: TyKind, span: Span) -> Ty {\n        Ty {\n            kind: self,\n            span: Some(span),\n            name: None,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumAsInner, JsonSchema)]\npub enum TyTupleField {\n    /// Named tuple element.\n    Single(Option<String>, Option<Ty>),\n\n    /// Placeholder for possibly many elements.\n    /// Means \"and other unmentioned columns\". Does not mean \"all columns\".\n    Wildcard(Option<Ty>),\n}\n\n/// Built-in sets.\n#[derive(\n    Debug,\n    Clone,\n    Serialize,\n    Deserialize,\n    PartialEq,\n    Eq,\n    strum::EnumString,\n    strum::Display,\n    JsonSchema,\n)]\npub enum PrimitiveSet {\n    #[strum(to_string = \"int\")]\n    Int,\n    #[strum(to_string = \"float\")]\n    Float,\n    #[strum(to_string = \"bool\")]\n    Bool,\n    #[strum(to_string = \"text\")]\n    Text,\n    #[strum(to_string = \"date\")]\n    Date,\n    #[strum(to_string = \"time\")]\n    Time,\n    #[strum(to_string = \"timestamp\")]\n    Timestamp,\n}\n\n// Type of a function\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]\npub struct TyFunc {\n    pub name_hint: Option<Ident>,\n    pub params: Vec<Option<Ty>>,\n    pub return_ty: Option<Box<Ty>>,\n}\n\nimpl Ty {\n    pub fn new<K: Into<TyKind>>(kind: K) -> Ty {\n        Ty {\n            kind: kind.into(),\n            span: None,\n            name: None,\n        }\n    }\n\n    pub fn relation(tuple_fields: Vec<TyTupleField>) -> Self {\n        let tuple = Ty::new(TyKind::Tuple(tuple_fields));\n        Ty::new(TyKind::Array(Some(Box::new(tuple))))\n    }\n\n    pub fn as_relation(&self) -> Option<&Vec<TyTupleField>> {\n        self.kind.as_array()?.as_ref()?.kind.as_tuple()\n    }\n\n    pub fn as_relation_mut(&mut self) -> Option<&mut Vec<TyTupleField>> {\n        self.kind.as_array_mut()?.as_mut()?.kind.as_tuple_mut()\n    }\n\n    pub fn into_relation(self) -> Option<Vec<TyTupleField>> {\n        self.kind.into_array().ok()??.kind.into_tuple().ok()\n    }\n\n    pub fn is_relation(&self) -> bool {\n        match &self.kind {\n            TyKind::Array(Some(elem)) => {\n                matches!(elem.kind, TyKind::Tuple(_))\n            }\n            _ => false,\n        }\n    }\n}\n\nimpl TyTupleField {\n    pub fn ty(&self) -> Option<&Ty> {\n        match self {\n            TyTupleField::Single(_, ty) => ty.as_ref(),\n            TyTupleField::Wildcard(ty) => ty.as_ref(),\n        }\n    }\n}\n\nimpl From<PrimitiveSet> for TyKind {\n    fn from(value: PrimitiveSet) -> Self {\n        TyKind::Primitive(value)\n    }\n}\n\nimpl From<TyFunc> for TyKind {\n    fn from(value: TyFunc) -> Self {\n        TyKind::Function(Some(value))\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/stmt.rs",
    "content": "use std::collections::HashMap;\n\nuse chumsky;\nuse chumsky::input::BorrowInput;\nuse chumsky::prelude::*;\nuse itertools::Itertools;\nuse semver::VersionReq;\n\nuse super::expr::{expr, expr_call, ident, pipeline};\nuse super::{ctrl, ident_part, into_stmt, keyword, new_line, pipe, with_doc_comment};\nuse crate::lexer::lr;\nuse crate::lexer::lr::{Literal, TokenKind};\nuse crate::parser::pr::*;\nuse crate::parser::types::type_expr;\nuse crate::span::Span;\n\nuse super::ParserError;\n\n/// The top-level parser for a PRQL file\npub fn source<'a, I>() -> impl Parser<'a, I, Vec<Stmt>, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a> + chumsky::input::ValueInput<'a>,\n{\n    with_doc_comment(query_def())\n        .or_not()\n        .map(|opt| opt.into_iter().collect::<Vec<_>>())\n        .then(module_contents())\n        .map(|(mut first, mut second)| {\n            first.append(&mut second);\n            first\n        })\n        // This is the only instance we can consume newlines at the end of something, since\n        // this is the end of the file\n        .then_ignore(new_line().repeated().collect::<Vec<_>>())\n        .then_ignore(end())\n        .boxed()\n}\n\nfn module_contents<'a, I>() -> impl Parser<'a, I, Vec<Stmt>, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a> + chumsky::input::ValueInput<'a>,\n{\n    recursive(|module_contents| {\n        let module_def = keyword(\"module\")\n            .ignore_then(ident_part())\n            .then(\n                module_contents\n                    .then_ignore(new_line().repeated().collect::<Vec<_>>())\n                    .delimited_by(ctrl('{'), ctrl('}')),\n            )\n            .map(|(name, stmts)| StmtKind::ModuleDef(ModuleDef { name, stmts }))\n            .labelled(\"module definition\");\n\n        let annotation = new_line()\n            .repeated()\n            .at_least(1)\n            .collect::<Vec<_>>()\n            .ignore_then(\n                select_ref! { lr::Token { kind: TokenKind::Annotate, .. } => () }\n                    .ignore_then(expr())\n                    .map(|expr| Annotation {\n                        expr: Box::new(expr),\n                    }),\n            )\n            .labelled(\"annotation\");\n\n        // TODO: we want to confirm that we're not allowing things on the same\n        // line that should't be; e.g. `let foo = 5 let bar = 6`. We can't\n        // enforce a new line here because then `module two {let houses =\n        // both.alike}` fails (though we could force a new line after the\n        // `module` if we wanted to?)\n        //\n        // let stmt_kind = new_line().repeated().at_least(1).ignore_then(choice((\n        let stmt_kind = new_line()\n            .repeated()\n            .collect::<Vec<_>>()\n            .ignore_then(choice((module_def, type_def(), import_def(), var_def())));\n\n        // Currently doc comments need to be before the annotation; probably\n        // should relax this?\n        with_doc_comment(\n            annotation\n                .repeated()\n                .collect::<Vec<_>>()\n                .then(stmt_kind)\n                .map_with(|(annotations, kind), extra| {\n                    into_stmt((annotations, kind), extra.span())\n                }),\n        )\n        .repeated()\n        .collect()\n    })\n    .boxed()\n}\n\nfn query_def<'a, I>() -> impl Parser<'a, I, Stmt, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a> + chumsky::input::ValueInput<'a>,\n{\n    new_line()\n        .repeated()\n        .at_least(1)\n        .collect::<Vec<_>>()\n        .ignore_then(keyword(\"prql\"))\n        .ignore_then(\n            // named arg\n            ident_part()\n                .then_ignore(ctrl(':'))\n                .then(expr())\n                .repeated()\n                .collect::<Vec<_>>(),\n        )\n        .then_ignore(new_line())\n        .validate(|args, extra, emit| {\n            let span = extra.span();\n            let mut args: HashMap<_, _> = args.into_iter().collect();\n\n            let version = args.remove(\"version\").and_then(|v| match v.kind {\n                ExprKind::Literal(Literal::String(v)) => match VersionReq::parse(&v) {\n                    Ok(ver) => Some(ver),\n                    Err(e) => {\n                        emit.emit(Rich::custom(span, e.to_string()));\n                        None\n                    }\n                },\n                _ => {\n                    emit.emit(Rich::custom(span, \"version must be a string literal\"));\n                    None\n                }\n            });\n\n            // TODO: `QueryDef` is currently implemented as `version` & `other`\n            // fields. We want to raise an error if an unsupported field is\n            // used, to avoid confusion (e.g. if someone passes `dialect`). So\n            // at the moment we implement this as having a HashMap with 0 or 1\n            // entries... We can decide how to implement `QueryDef` later, and\n            // have this awkward construction in the meantime.\n            let other = args\n                .remove(\"target\")\n                .and_then(|v| {\n                    if let ExprKind::Ident(name) = v.kind {\n                        Some(name.to_string())\n                    } else {\n                        emit.emit(Rich::custom(span, \"target must be a string literal\"));\n                        None\n                    }\n                })\n                .map_or_else(HashMap::new, |x| {\n                    HashMap::from_iter(vec![(\"target\".to_string(), x)])\n                });\n\n            if !args.is_empty() {\n                emit.emit(Rich::custom(\n                    span,\n                    format!(\n                        \"unknown query definition arguments {}\",\n                        args.keys().map(|x| format!(\"`{x}`\")).join(\", \")\n                    ),\n                ));\n            }\n\n            StmtKind::QueryDef(Box::new(QueryDef { version, other }))\n        })\n        .map(|kind| (Vec::new(), kind))\n        .map_with(|(annotations, kind), extra| into_stmt((annotations, kind), extra.span()))\n        .labelled(\"query header\")\n        .boxed()\n}\n\n/// A variable definition could be any of:\n/// - `let foo = 5`\n/// - `from artists` — captured as a \"main\"\n/// - `from artists | into x` — captured as an \"into\"`\nfn var_def<'a, I>() -> impl Parser<'a, I, StmtKind, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a> + chumsky::input::ValueInput<'a>,\n{\n    let let_ = new_line()\n        .repeated()\n        .collect::<Vec<_>>()\n        .ignore_then(keyword(\"let\"))\n        .ignore_then(ident_part())\n        .then(type_expr().delimited_by(ctrl('<'), ctrl('>')).or_not())\n        .then(ctrl('=').ignore_then(expr_call()).map(Box::new).or_not())\n        .map(|((name, ty), value)| {\n            StmtKind::VarDef(VarDef {\n                name,\n                value,\n                ty,\n                kind: VarDefKind::Let,\n            })\n        })\n        .labelled(\"variable definition\");\n\n    let main_or_into = new_line()\n        .repeated()\n        .collect::<Vec<_>>()\n        .ignore_then(pipeline(expr_call()))\n        .map(Box::new)\n        .then(\n            pipe()\n                .ignore_then(keyword(\"into\").ignore_then(ident_part()))\n                .or_not(),\n        )\n        .map(|(value, name)| {\n            let kind = if name.is_none() {\n                VarDefKind::Main\n            } else {\n                VarDefKind::Into\n            };\n            let name = name.unwrap_or_else(|| \"main\".to_string());\n\n            StmtKind::VarDef(VarDef {\n                name,\n                kind,\n                value: Some(value),\n                ty: None,\n            })\n        });\n\n    let_.or(main_or_into).boxed()\n}\n\nfn type_def<'a, I>() -> impl Parser<'a, I, StmtKind, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a> + chumsky::input::ValueInput<'a>,\n{\n    keyword(\"type\")\n        .ignore_then(ident_part())\n        .then(ctrl('=').ignore_then(type_expr()))\n        .map(|(name, value)| StmtKind::TypeDef(TypeDef { name, value }))\n        .labelled(\"type definition\")\n}\n\nfn import_def<'a, I>() -> impl Parser<'a, I, StmtKind, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a> + chumsky::input::ValueInput<'a>,\n{\n    keyword(\"import\")\n        .ignore_then(ident_part().then_ignore(ctrl('=')).or_not())\n        .then(ident())\n        .map(|(alias, name)| StmtKind::ImportDef(ImportDef { name, alias }))\n        .labelled(\"import statement\")\n}\n\n#[cfg(test)]\nmod tests {\n    use chumsky::prelude::*;\n    use insta::{assert_debug_snapshot, assert_yaml_snapshot};\n\n    use super::*;\n    use crate::error::Error;\n\n    fn parse_module_contents(source: &str) -> Result<Vec<Stmt>, Vec<Error>> {\n        crate::parse_test!(\n            source,\n            module_contents()\n                .then_ignore(new_line().repeated())\n                .then_ignore(end())\n        )\n    }\n\n    fn parse_var_def(source: &str) -> Result<StmtKind, Vec<Error>> {\n        crate::parse_test!(\n            source,\n            var_def()\n                .then_ignore(new_line().repeated())\n                .then_ignore(end())\n        )\n    }\n\n    fn parse_module_contents_complete(source: &str) -> Result<Vec<Stmt>, Vec<Error>> {\n        crate::parse_test!(source, module_contents().then_ignore(end()))\n    }\n\n    #[test]\n    fn test_module_contents() {\n        assert_yaml_snapshot!(parse_module_contents(r#\"\n            let world = 1\n            let man = module.world\n        \"#).unwrap(), @r#\"\n        - VarDef:\n            kind: Let\n            name: world\n            value:\n              Literal:\n                Integer: 1\n              span: \"0:25-26\"\n          span: \"0:0-26\"\n        - VarDef:\n            kind: Let\n            name: man\n            value:\n              Ident:\n                - module\n                - world\n              span: \"0:49-61\"\n          span: \"0:26-61\"\n        \"#);\n    }\n\n    #[test]\n    fn into() {\n        assert_yaml_snapshot!(parse_var_def(r#\"\n            from artists\n            into x\n        \"#).unwrap(), @r#\"\n        VarDef:\n          kind: Into\n          name: x\n          value:\n            FuncCall:\n              name:\n                Ident:\n                  - from\n                span: \"0:13-17\"\n              args:\n                - Ident:\n                    - artists\n                  span: \"0:18-25\"\n            span: \"0:13-25\"\n        \"#);\n\n        assert_yaml_snapshot!(parse_var_def(r#\"\n            from artists | into x\n        \"#).unwrap(), @r#\"\n        VarDef:\n          kind: Into\n          name: x\n          value:\n            FuncCall:\n              name:\n                Ident:\n                  - from\n                span: \"0:13-17\"\n              args:\n                - Ident:\n                    - artists\n                  span: \"0:18-25\"\n            span: \"0:13-25\"\n        \"#);\n    }\n\n    #[test]\n    fn let_into() {\n        assert_debug_snapshot!(parse_module_contents_complete(r#\"\n        let y = (\n            from artists\n            into x\n        )\n        \"#).unwrap_err(), @r#\"\n        [\n            Error {\n                kind: Error,\n                span: Some(\n                    0:56-60,\n                ),\n                reason: Expected {\n                    who: None,\n                    expected: \"one of doc comment, function call, function definition, new line or something else\",\n                    found: \"keyword into\",\n                },\n                hints: [],\n                code: None,\n            },\n            Error {\n                kind: Error,\n                span: Some(\n                    0:0-73,\n                ),\n                reason: Simple(\n                    \"Expected one of import statement, module definition, new line, pipeline, something else, type definition or variable definition, but didn't find anything before the end.\",\n                ),\n                hints: [],\n                code: None,\n            },\n        ]\n        \"#);\n    }\n\n    #[test]\n    fn test_module() {\n        let parse_module = |s| parse_module_contents(s).unwrap();\n\n        let module_ast = parse_module(\n            r#\"\n          module hello {\n            let world = 1\n            let man = module.world\n          }\n        \"#,\n        );\n\n        assert_yaml_snapshot!(module_ast, @r#\"\n        - ModuleDef:\n            name: hello\n            stmts:\n              - VarDef:\n                  kind: Let\n                  name: world\n                  value:\n                    Literal:\n                      Integer: 1\n                    span: \"0:50-51\"\n                span: \"0:25-51\"\n              - VarDef:\n                  kind: Let\n                  name: man\n                  value:\n                    Ident:\n                      - module\n                      - world\n                    span: \"0:74-86\"\n                span: \"0:51-86\"\n          span: \"0:0-98\"\n        \"#);\n\n        // Check this parses OK. (We tried comparing it to the AST of the result\n        // above, but the span information was different, so we just check it.\n        // Would be nice to be able to strip spans...)\n        parse_module(\n            r#\"\n\n          module hello {\n\n\n            let world = 1\n\n            let man = module.world\n\n          }\n        \"#,\n        );\n    }\n\n    #[test]\n    fn test_module_def() {\n        // Same line\n        assert_yaml_snapshot!(parse_module_contents(r#\"module two {let houses = both.alike}\n        \"#).unwrap(), @r#\"\n        - ModuleDef:\n            name: two\n            stmts:\n              - VarDef:\n                  kind: Let\n                  name: houses\n                  value:\n                    Ident:\n                      - both\n                      - alike\n                    span: \"0:25-35\"\n                span: \"0:12-35\"\n          span: \"0:0-36\"\n        \"#);\n\n        assert_yaml_snapshot!(parse_module_contents(r#\"\n          module dignity {\n            let fair = 1\n            let verona = we.lay\n         }\n        \"#).unwrap(), @r#\"\n        - ModuleDef:\n            name: dignity\n            stmts:\n              - VarDef:\n                  kind: Let\n                  name: fair\n                  value:\n                    Literal:\n                      Integer: 1\n                    span: \"0:51-52\"\n                span: \"0:27-52\"\n              - VarDef:\n                  kind: Let\n                  name: verona\n                  value:\n                    Ident:\n                      - we\n                      - lay\n                    span: \"0:78-84\"\n                span: \"0:52-84\"\n          span: \"0:0-95\"\n        \"#);\n    }\n\n    #[test]\n    fn doc_comment_module() {\n        assert_yaml_snapshot!(parse_module_contents(r#\"\n\n        #! first doc comment\n        from foo\n\n        \"#).unwrap(), @r#\"\n        - VarDef:\n            kind: Main\n            name: main\n            value:\n              FuncCall:\n                name:\n                  Ident:\n                    - from\n                  span: \"0:39-43\"\n                args:\n                  - Ident:\n                      - foo\n                    span: \"0:44-47\"\n              span: \"0:39-47\"\n          span: \"0:30-47\"\n          doc_comment: \" first doc comment\"\n        \"#);\n\n        assert_yaml_snapshot!(parse_module_contents(r#\"\n\n\n        #! first doc comment\n        from foo\n        into x\n\n        #! second doc comment\n        from bar\n\n        \"#).unwrap(), @r#\"\n        - VarDef:\n            kind: Into\n            name: x\n            value:\n              FuncCall:\n                name:\n                  Ident:\n                    - from\n                  span: \"0:40-44\"\n                args:\n                  - Ident:\n                      - foo\n                    span: \"0:45-48\"\n              span: \"0:40-48\"\n          span: \"0:31-63\"\n          doc_comment: \" first doc comment\"\n        - VarDef:\n            kind: Main\n            name: main\n            value:\n              FuncCall:\n                name:\n                  Ident:\n                    - from\n                  span: \"0:103-107\"\n                args:\n                  - Ident:\n                      - bar\n                    span: \"0:108-111\"\n              span: \"0:103-111\"\n          span: \"0:94-111\"\n          doc_comment: \" second doc comment\"\n        \"#);\n    }\n\n    #[test]\n    fn doc_comment_inline_module() {\n        // Check the newline doesn't get eated by the `{}` of the module\n        // TODO: could give a better error when we forget the module name\n        assert_yaml_snapshot!(parse_module_contents(r#\"\n        module bar {\n          #! first doc comment\n          from foo\n        }\n        \"#).unwrap(), @r#\"\n        - ModuleDef:\n            name: bar\n            stmts:\n              - VarDef:\n                  kind: Main\n                  name: main\n                  value:\n                    FuncCall:\n                      name:\n                        Ident:\n                          - from\n                        span: \"0:63-67\"\n                      args:\n                        - Ident:\n                            - foo\n                          span: \"0:68-71\"\n                    span: \"0:63-71\"\n                span: \"0:52-71\"\n                doc_comment: \" first doc comment\"\n          span: \"0:0-81\"\n        \"#);\n    }\n\n    #[test]\n    fn lambdas() {\n        assert_yaml_snapshot!(parse_module_contents(r#\"\n        let first = column <array> -> internal std.first\n        \"#).unwrap(), @r#\"\n        - VarDef:\n            kind: Let\n            name: first\n            value:\n              Func:\n                return_ty: ~\n                body:\n                  Internal: std.first\n                  span: \"0:39-57\"\n                params:\n                  - name: column\n                    ty:\n                      kind:\n                        Ident:\n                          - array\n                      span: \"0:29-34\"\n                      name: ~\n                    default_value: ~\n                named_params: []\n              span: \"0:21-57\"\n          span: \"0:0-57\"\n        \"#);\n\n        assert_yaml_snapshot!(parse_module_contents(r#\"\n      module defs {\n        let first = column <array> -> internal std.first\n        let last  = column <array> -> internal std.last\n    }\n        \"#).unwrap(), @r#\"\n        - ModuleDef:\n            name: defs\n            stmts:\n              - VarDef:\n                  kind: Let\n                  name: first\n                  value:\n                    Func:\n                      return_ty: ~\n                      body:\n                        Internal: std.first\n                        span: \"0:59-77\"\n                      params:\n                        - name: column\n                          ty:\n                            kind:\n                              Ident:\n                                - array\n                            span: \"0:49-54\"\n                            name: ~\n                          default_value: ~\n                      named_params: []\n                    span: \"0:41-77\"\n                span: \"0:20-77\"\n              - VarDef:\n                  kind: Let\n                  name: last\n                  value:\n                    Func:\n                      return_ty: ~\n                      body:\n                        Internal: std.last\n                        span: \"0:116-133\"\n                      params:\n                        - name: column\n                          ty:\n                            kind:\n                              Ident:\n                                - array\n                            span: \"0:106-111\"\n                            name: ~\n                          default_value: ~\n                      named_params: []\n                    span: \"0:98-133\"\n                span: \"0:77-133\"\n          span: \"0:0-139\"\n        \"#);\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/test.rs",
    "content": "use insta::assert_yaml_snapshot;\n\nuse super::pr::{Expr, FuncCall};\nuse crate::error::Error;\n\n/// Macro to eliminate test helper boilerplate.\n/// Converts source code to the parsed input format that our parsers expect.\n#[macro_export]\nmacro_rules! parse_test {\n    ($source:expr, $parser:expr) => {{\n        #[allow(unused_imports)]\n        use chumsky::input::Input as _;\n        #[allow(unused_imports)]\n        use chumsky::IterParser as _;\n        #[allow(unused_imports)]\n        use chumsky::Parser as _;\n\n        let tokens = $crate::lexer::lex_source($source)?;\n        let semantic_tokens: Vec<_> = tokens\n            .0\n            .into_iter()\n            .filter(|token| {\n                !matches!(\n                    token.kind,\n                    $crate::lexer::lr::TokenKind::Comment(_)\n                        | $crate::lexer::lr::TokenKind::LineWrap(_)\n                )\n            })\n            .collect();\n\n        let input =\n            semantic_tokens\n                .as_slice()\n                .map_span(|simple_span: chumsky::span::SimpleSpan| {\n                    let start_idx = simple_span.start;\n                    let end_idx = simple_span.end;\n\n                    let start = semantic_tokens\n                        .get(start_idx)\n                        .map(|t| t.span.start)\n                        .unwrap_or(0);\n                    let end = semantic_tokens\n                        .get(end_idx.saturating_sub(1))\n                        .map(|t| t.span.end)\n                        .unwrap_or(start);\n\n                    $crate::span::Span {\n                        start,\n                        end,\n                        source_id: 0,\n                    }\n                });\n\n        let (ast, errors) = $parser.parse(input).into_output_errors();\n        if !errors.is_empty() {\n            return Err(errors.into_iter().map(Into::into).collect());\n        }\n        Ok(ast.unwrap())\n    }};\n}\n\nfn parse_expr(source: &str) -> Result<Expr, Vec<Error>> {\n    parse_test!(\n        source,\n        super::new_line()\n            .repeated()\n            .collect::<Vec<_>>()\n            .ignore_then(super::expr::expr_call())\n    )\n}\n\n#[test]\nfn test_ranges() {\n    assert_yaml_snapshot!(parse_expr(r#\"3..5\"#).unwrap(), @r#\"\n    Range:\n      start:\n        Literal:\n          Integer: 3\n        span: \"0:0-1\"\n      end:\n        Literal:\n          Integer: 5\n        span: \"0:3-4\"\n    span: \"0:0-4\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"-2..-5\"#).unwrap(), @r#\"\n    Range:\n      start:\n        Unary:\n          op: Neg\n          expr:\n            Literal:\n              Integer: 2\n            span: \"0:1-2\"\n        span: \"0:0-2\"\n      end:\n        Unary:\n          op: Neg\n          expr:\n            Literal:\n              Integer: 5\n            span: \"0:5-6\"\n        span: \"0:4-6\"\n    span: \"0:0-6\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"(-2..(-5 | abs))\"#).unwrap(), @r#\"\n    Range:\n      start:\n        Unary:\n          op: Neg\n          expr:\n            Literal:\n              Integer: 2\n            span: \"0:2-3\"\n        span: \"0:1-3\"\n      end:\n        Pipeline:\n          exprs:\n            - Unary:\n                op: Neg\n                expr:\n                  Literal:\n                    Integer: 5\n                  span: \"0:7-8\"\n              span: \"0:6-8\"\n            - Ident:\n                - abs\n              span: \"0:11-14\"\n        span: \"0:6-14\"\n    span: \"0:0-16\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"(2 + 5)..'a'\"#).unwrap(), @r#\"\n    Range:\n      start:\n        Binary:\n          left:\n            Literal:\n              Integer: 2\n            span: \"0:1-2\"\n          op: Add\n          right:\n            Literal:\n              Integer: 5\n            span: \"0:5-6\"\n        span: \"0:1-6\"\n      end:\n        Literal:\n          String: a\n        span: \"0:9-12\"\n    span: \"0:0-12\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"1.6..rel.col\"#).unwrap(), @r#\"\n    Range:\n      start:\n        Literal:\n          Float: 1.6\n        span: \"0:0-3\"\n      end:\n        Ident:\n          - rel\n          - col\n        span: \"0:5-12\"\n    span: \"0:0-12\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"6..\"#).unwrap(), @r#\"\n    Range:\n      start:\n        Literal:\n          Integer: 6\n        span: \"0:0-1\"\n      end: ~\n    span: \"0:0-3\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"..7\"#).unwrap(), @r#\"\n    Range:\n      start: ~\n      end:\n        Literal:\n          Integer: 7\n        span: \"0:2-3\"\n    span: \"0:0-3\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"..\"#).unwrap(), @r#\"\n    Range:\n      start: ~\n      end: ~\n    span: \"0:0-2\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"@2020-01-01..@2021-01-01\"#).unwrap(), @r#\"\n    Range:\n      start:\n        Literal:\n          Date: 2020-01-01\n        span: \"0:0-11\"\n      end:\n        Literal:\n          Date: 2021-01-01\n        span: \"0:13-24\"\n    span: \"0:0-24\"\n    \"#);\n}\n\n#[test]\nfn test_basic_exprs() {\n    assert_yaml_snapshot!(parse_expr(r#\"country == \"USA\"\"#).unwrap(), @r#\"\n    Binary:\n      left:\n        Ident:\n          - country\n        span: \"0:0-7\"\n      op: Eq\n      right:\n        Literal:\n          String: USA\n        span: \"0:11-16\"\n    span: \"0:0-16\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(\"select {a, b, c}\").unwrap(), @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - select\n        span: \"0:0-6\"\n      args:\n        - Tuple:\n            - Ident:\n                - a\n              span: \"0:8-9\"\n            - Ident:\n                - b\n              span: \"0:11-12\"\n            - Ident:\n                - c\n              span: \"0:14-15\"\n          span: \"0:7-16\"\n    span: \"0:0-16\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(\n            \"group {title, country} (\n                aggregate {sum salary}\n            )\"\n        ).unwrap(), @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - group\n        span: \"0:0-5\"\n      args:\n        - Tuple:\n            - Ident:\n                - title\n              span: \"0:7-12\"\n            - Ident:\n                - country\n              span: \"0:14-21\"\n          span: \"0:6-22\"\n        - FuncCall:\n            name:\n              Ident:\n                - aggregate\n              span: \"0:41-50\"\n            args:\n              - Tuple:\n                  - FuncCall:\n                      name:\n                        Ident:\n                          - sum\n                        span: \"0:52-55\"\n                      args:\n                        - Ident:\n                            - salary\n                          span: \"0:56-62\"\n                    span: \"0:52-62\"\n                span: \"0:51-63\"\n          span: \"0:41-63\"\n    span: \"0:0-77\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(\n            r#\"    filter country == \"USA\"\"#\n        ).unwrap(), @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - filter\n        span: \"0:4-10\"\n      args:\n        - Binary:\n            left:\n              Ident:\n                - country\n              span: \"0:11-18\"\n            op: Eq\n            right:\n              Literal:\n                String: USA\n              span: \"0:22-27\"\n          span: \"0:11-27\"\n    span: \"0:4-27\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(\"{a, b, c,}\").unwrap(), @r#\"\n    Tuple:\n      - Ident:\n          - a\n        span: \"0:1-2\"\n      - Ident:\n          - b\n        span: \"0:4-5\"\n      - Ident:\n          - c\n        span: \"0:7-8\"\n    span: \"0:0-10\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(\n            r#\"{\n  gross_salary = salary + payroll_tax,\n  gross_cost   = gross_salary + benefits_cost\n}\"#\n        ).unwrap(), @r#\"\n    Tuple:\n      - Binary:\n          left:\n            Ident:\n              - salary\n            span: \"0:19-25\"\n          op: Add\n          right:\n            Ident:\n              - payroll_tax\n            span: \"0:28-39\"\n        span: \"0:19-39\"\n        alias: gross_salary\n      - Binary:\n          left:\n            Ident:\n              - gross_salary\n            span: \"0:58-70\"\n          op: Add\n          right:\n            Ident:\n              - benefits_cost\n            span: \"0:73-86\"\n        span: \"0:58-86\"\n        alias: gross_cost\n    span: \"0:0-88\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(\n            \"join side:left country (id==employee_id)\"\n        ).unwrap(), @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - join\n        span: \"0:0-4\"\n      args:\n        - Ident:\n            - country\n          span: \"0:15-22\"\n        - Binary:\n            left:\n              Ident:\n                - id\n              span: \"0:24-26\"\n            op: Eq\n            right:\n              Ident:\n                - employee_id\n              span: \"0:28-39\"\n          span: \"0:24-39\"\n      named_args:\n        side:\n          Ident:\n            - left\n          span: \"0:10-14\"\n    span: \"0:0-40\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(\"1  + 2\").unwrap(), @r#\"\n    Binary:\n      left:\n        Literal:\n          Integer: 1\n        span: \"0:0-1\"\n      op: Add\n      right:\n        Literal:\n          Integer: 2\n        span: \"0:5-6\"\n    span: \"0:0-6\"\n    \"#)\n}\n\n#[test]\nfn test_string() {\n    let double_quoted_ast = parse_expr(r#\"\" U S A \"\"#).unwrap();\n    assert_yaml_snapshot!(double_quoted_ast, @r#\"\n    Literal:\n      String: \" U S A \"\n    span: \"0:0-9\"\n    \"#);\n\n    let single_quoted_ast = parse_expr(r#\"' U S A '\"#).unwrap();\n    assert_eq!(single_quoted_ast, double_quoted_ast);\n\n    // Single quotes within double quotes should produce a string containing\n    // the single quotes (and vice versa).\n    assert_yaml_snapshot!(parse_expr(r#\"\"' U S A '\"\"#).unwrap(), @r#\"\n    Literal:\n      String: \"' U S A '\"\n    span: \"0:0-11\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"'\" U S A \"'\"#).unwrap(), @r#\"\n    Literal:\n      String: \"\\\" U S A \\\"\"\n    span: \"0:0-11\"\n    \"#);\n\n    parse_expr(r#\"\" U S A\"#).unwrap_err();\n    parse_expr(r#\"\" U S A '\"#).unwrap_err();\n\n    assert_yaml_snapshot!(parse_expr(r#\"\" \\nU S A \"\"#).unwrap(), @r#\"\n    Literal:\n      String: \" \\nU S A \"\n    span: \"0:0-11\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"r\" \\nU S A \"\"#).unwrap(), @r#\"\n    Literal:\n      RawString: \" \\\\nU S A \"\n    span: \"0:0-12\"\n    \"#);\n\n    let multi_double = parse_expr(\n        r#\"\"\"\"\n''\nCanada\n\"\n\n\"\"\"\"#,\n    )\n    .unwrap();\n    assert_yaml_snapshot!(multi_double, @r#\"\n    Literal:\n      String: \"\\n''\\nCanada\\n\\\"\\n\\n\"\n    span: \"0:0-20\"\n    \"#);\n\n    let multi_single = parse_expr(\n        r#\"'''\nCanada\n\"\n\"\"\"\n\n'''\"#,\n    )\n    .unwrap();\n    assert_yaml_snapshot!(multi_single, @r#\"\n    Literal:\n      String: \"\\nCanada\\n\\\"\\n\\\"\\\"\\\"\\n\\n\"\n    span: \"0:0-21\"\n    \"#);\n\n    assert_yaml_snapshot!(\n          parse_expr(\"''\").unwrap(),\n          @r#\"\n    Literal:\n      String: \"\"\n    span: \"0:0-2\"\n    \"#);\n}\n\n#[test]\nfn test_s_string() {\n    assert_yaml_snapshot!(parse_expr(r#\"s\"SUM({col})\"\"#).unwrap(), @r#\"\n    SString:\n      - String: SUM(\n      - Expr:\n          expr:\n            Ident:\n              - col\n            span: \"0:7-10\"\n          format: ~\n      - String: )\n    span: \"0:0-13\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"s\"SUM({rel.`Col name`})\"\"#).unwrap(), @r#\"\n    SString:\n      - String: SUM(\n      - Expr:\n          expr:\n            Ident:\n              - rel\n              - Col name\n            span: \"0:7-21\"\n          format: ~\n      - String: )\n    span: \"0:0-24\"\n    \"#)\n}\n\n#[test]\nfn test_s_string_braces() {\n    assert_yaml_snapshot!(parse_expr(r#\"s\"{{?crystal_var}}\"\"#).unwrap(), @r#\"\n    SString:\n      - String: \"{?crystal_var}\"\n    span: \"0:0-19\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"s\"foo{{bar\"\"#).unwrap(), @r#\"\n    SString:\n      - String: \"foo{bar\"\n    span: \"0:0-11\"\n    \"#);\n    parse_expr(r#\"s\"foo{{bar}\"\"#).unwrap_err();\n}\n\n#[test]\nfn test_tuple() {\n    assert_yaml_snapshot!(parse_expr(r#\"{1 + 1, 2}\"#).unwrap(), @r#\"\n    Tuple:\n      - Binary:\n          left:\n            Literal:\n              Integer: 1\n            span: \"0:1-2\"\n          op: Add\n          right:\n            Literal:\n              Integer: 1\n            span: \"0:5-6\"\n        span: \"0:1-6\"\n      - Literal:\n          Integer: 2\n        span: \"0:8-9\"\n    span: \"0:0-10\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"{1 + (f 1), 2}\"#).unwrap(), @r#\"\n    Tuple:\n      - Binary:\n          left:\n            Literal:\n              Integer: 1\n            span: \"0:1-2\"\n          op: Add\n          right:\n            FuncCall:\n              name:\n                Ident:\n                  - f\n                span: \"0:6-7\"\n              args:\n                - Literal:\n                    Integer: 1\n                  span: \"0:8-9\"\n            span: \"0:6-9\"\n        span: \"0:1-10\"\n      - Literal:\n          Integer: 2\n        span: \"0:12-13\"\n    span: \"0:0-14\"\n    \"#);\n    // Line breaks\n    assert_yaml_snapshot!(parse_expr(\n            r#\"{1,\n\n                2}\"#\n        ).unwrap(), @r#\"\n    Tuple:\n      - Literal:\n          Integer: 1\n        span: \"0:1-2\"\n      - Literal:\n          Integer: 2\n        span: \"0:21-22\"\n    span: \"0:0-23\"\n    \"#);\n    // Function call in a tuple\n    let ab = parse_expr(r#\"{a b}\"#).unwrap();\n    let a_comma_b = parse_expr(r#\"{a, b}\"#).unwrap();\n    assert_yaml_snapshot!(ab, @r#\"\n    Tuple:\n      - FuncCall:\n          name:\n            Ident:\n              - a\n            span: \"0:1-2\"\n          args:\n            - Ident:\n                - b\n              span: \"0:3-4\"\n        span: \"0:1-4\"\n    span: \"0:0-5\"\n    \"#);\n    assert_yaml_snapshot!(a_comma_b, @r#\"\n    Tuple:\n      - Ident:\n          - a\n        span: \"0:1-2\"\n      - Ident:\n          - b\n        span: \"0:4-5\"\n    span: \"0:0-6\"\n    \"#);\n    assert_ne!(ab, a_comma_b);\n\n    assert_yaml_snapshot!(parse_expr(r#\"{amount, +amount, -amount}\"#).unwrap(), @r#\"\n    Tuple:\n      - Ident:\n          - amount\n        span: \"0:1-7\"\n      - Unary:\n          op: Add\n          expr:\n            Ident:\n              - amount\n            span: \"0:10-16\"\n        span: \"0:9-16\"\n      - Unary:\n          op: Neg\n          expr:\n            Ident:\n              - amount\n            span: \"0:19-25\"\n        span: \"0:18-25\"\n    span: \"0:0-26\"\n    \"#);\n    // Operators in tuple items\n    assert_yaml_snapshot!(parse_expr(r#\"{amount, +amount, -amount}\"#).unwrap(), @r#\"\n    Tuple:\n      - Ident:\n          - amount\n        span: \"0:1-7\"\n      - Unary:\n          op: Add\n          expr:\n            Ident:\n              - amount\n            span: \"0:10-16\"\n        span: \"0:9-16\"\n      - Unary:\n          op: Neg\n          expr:\n            Ident:\n              - amount\n            span: \"0:19-25\"\n        span: \"0:18-25\"\n    span: \"0:0-26\"\n    \"#);\n}\n\n#[test]\nfn test_number() {\n    assert_yaml_snapshot!(parse_expr(r#\"23\"#).unwrap(), @r#\"\n    Literal:\n      Integer: 23\n    span: \"0:0-2\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"2_3_4.5_6\"#).unwrap(), @r#\"\n    Literal:\n      Float: 234.56\n    span: \"0:0-9\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"23.6\"#).unwrap(), @r#\"\n    Literal:\n      Float: 23.6\n    span: \"0:0-4\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"23.0\"#).unwrap(), @r#\"\n    Literal:\n      Float: 23\n    span: \"0:0-4\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"2 + 2\"#).unwrap(), @r#\"\n    Binary:\n      left:\n        Literal:\n          Integer: 2\n        span: \"0:0-1\"\n      op: Add\n      right:\n        Literal:\n          Integer: 2\n        span: \"0:4-5\"\n    span: \"0:0-5\"\n    \"#);\n\n    // Underscores at the beginning are parsed as ident\n    assert!(parse_expr(\"_2\").unwrap().kind.into_ident().is_ok());\n    assert!(parse_expr(\"_\").unwrap().kind.into_ident().is_ok());\n\n    assert!(parse_expr(\"_2._3\").unwrap().kind.is_ident());\n\n    assert_yaml_snapshot!(parse_expr(r#\"2e3\"#).unwrap(), @r#\"\n    Literal:\n      Float: 2000\n    span: \"0:0-3\"\n    \"#);\n\n    // expr_of_string(\"2_\").unwrap_err(); // TODO\n    // expr_of_string(\"2.3_\").unwrap_err(); // TODO\n}\n\n#[test]\nfn test_derive() {\n    assert_yaml_snapshot!(\n            parse_expr(r#\"derive {x = 5, y = (-x)}\"#).unwrap()\n        , @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - derive\n        span: \"0:0-6\"\n      args:\n        - Tuple:\n            - Literal:\n                Integer: 5\n              span: \"0:12-13\"\n              alias: x\n            - Unary:\n                op: Neg\n                expr:\n                  Ident:\n                    - x\n                  span: \"0:21-22\"\n              span: \"0:19-23\"\n              alias: y\n          span: \"0:7-24\"\n    span: \"0:0-24\"\n    \"#);\n}\n\n#[test]\nfn test_select() {\n    assert_yaml_snapshot!(\n            parse_expr(r#\"select x\"#).unwrap()\n        , @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - select\n        span: \"0:0-6\"\n      args:\n        - Ident:\n            - x\n          span: \"0:7-8\"\n    span: \"0:0-8\"\n    \"#);\n\n    assert_yaml_snapshot!(\n            parse_expr(r#\"select !{x}\"#).unwrap()\n        , @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - select\n        span: \"0:0-6\"\n      args:\n        - Unary:\n            op: Not\n            expr:\n              Tuple:\n                - Ident:\n                    - x\n                  span: \"0:9-10\"\n              span: \"0:8-11\"\n          span: \"0:7-11\"\n    span: \"0:0-11\"\n    \"#);\n\n    assert_yaml_snapshot!(\n            parse_expr(r#\"select {x, y}\"#).unwrap()\n        , @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - select\n        span: \"0:0-6\"\n      args:\n        - Tuple:\n            - Ident:\n                - x\n              span: \"0:8-9\"\n            - Ident:\n                - y\n              span: \"0:11-12\"\n          span: \"0:7-13\"\n    span: \"0:0-13\"\n    \"#);\n}\n\n#[test]\nfn test_expr() {\n    assert_yaml_snapshot!(\n            parse_expr(r#\"country == \"USA\"\"#).unwrap()\n        , @r#\"\n    Binary:\n      left:\n        Ident:\n          - country\n        span: \"0:0-7\"\n      op: Eq\n      right:\n        Literal:\n          String: USA\n        span: \"0:11-16\"\n    span: \"0:0-16\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(\n                r#\"{\n  gross_salary = salary + payroll_tax,\n  gross_cost   = gross_salary + benefits_cost,\n}\"#).unwrap(), @r#\"\n    Tuple:\n      - Binary:\n          left:\n            Ident:\n              - salary\n            span: \"0:19-25\"\n          op: Add\n          right:\n            Ident:\n              - payroll_tax\n            span: \"0:28-39\"\n        span: \"0:19-39\"\n        alias: gross_salary\n      - Binary:\n          left:\n            Ident:\n              - gross_salary\n            span: \"0:58-70\"\n          op: Add\n          right:\n            Ident:\n              - benefits_cost\n            span: \"0:73-86\"\n        span: \"0:58-86\"\n        alias: gross_cost\n    span: \"0:0-89\"\n    \"#);\n    assert_yaml_snapshot!(\n            parse_expr(\n                \"(salary + payroll_tax) * (1 + tax_rate)\"\n            ).unwrap(),\n            @r#\"\n    Binary:\n      left:\n        Binary:\n          left:\n            Ident:\n              - salary\n            span: \"0:1-7\"\n          op: Add\n          right:\n            Ident:\n              - payroll_tax\n            span: \"0:10-21\"\n        span: \"0:1-21\"\n      op: Mul\n      right:\n        Binary:\n          left:\n            Literal:\n              Integer: 1\n            span: \"0:26-27\"\n          op: Add\n          right:\n            Ident:\n              - tax_rate\n            span: \"0:30-38\"\n        span: \"0:26-38\"\n    span: \"0:0-39\"\n    \"#);\n}\n\n#[test]\nfn test_regex() {\n    assert_yaml_snapshot!(\n            parse_expr(\n                \"'oba' ~= 'foobar'\"\n            ).unwrap(),\n            @r#\"\n    Binary:\n      left:\n        Literal:\n          String: oba\n        span: \"0:0-5\"\n      op: RegexSearch\n      right:\n        Literal:\n          String: foobar\n        span: \"0:9-17\"\n    span: \"0:0-17\"\n    \"#);\n}\n\n#[test]\nfn test_func_call() {\n    // Function without argument\n    let ast = parse_expr(r#\"count\"#).unwrap();\n    let ident = ast.kind.into_ident().unwrap();\n    assert_yaml_snapshot!(\n            ident, @\"- count\");\n\n    let ast = parse_expr(r#\"s 'foo'\"#).unwrap();\n    assert_yaml_snapshot!(\n            ast, @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - s\n        span: \"0:0-1\"\n      args:\n        - Literal:\n            String: foo\n          span: \"0:2-7\"\n    span: \"0:0-7\"\n    \"#);\n\n    // A non-friendly option for #154\n    let ast = parse_expr(r#\"count s'*'\"#).unwrap();\n    let func_call: FuncCall = ast.kind.into_func_call().unwrap();\n    assert_yaml_snapshot!(\n            func_call, @r#\"\n    name:\n      Ident:\n        - count\n      span: \"0:0-5\"\n    args:\n      - SString:\n          - String: \"*\"\n        span: \"0:6-10\"\n    \"#);\n\n    parse_expr(\"plus_one x:0 x:0 \").unwrap_err();\n\n    let ast = parse_expr(r#\"add bar to=3\"#).unwrap();\n    assert_yaml_snapshot!(\n            ast, @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - add\n        span: \"0:0-3\"\n      args:\n        - Ident:\n            - bar\n          span: \"0:4-7\"\n        - Literal:\n            Integer: 3\n          span: \"0:11-12\"\n          alias: to\n    span: \"0:0-12\"\n    \"#);\n}\n\n#[test]\nfn test_right_assoc() {\n    assert_yaml_snapshot!(parse_expr(r#\"2 ** 3 ** 4\"#).unwrap(), @r#\"\n    Binary:\n      left:\n        Literal:\n          Integer: 2\n        span: \"0:0-1\"\n      op: Pow\n      right:\n        Binary:\n          left:\n            Literal:\n              Integer: 3\n            span: \"0:5-6\"\n          op: Pow\n          right:\n            Literal:\n              Integer: 4\n            span: \"0:10-11\"\n        span: \"0:5-11\"\n    span: \"0:0-11\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(r#\"1 + 2 ** (3 + 4) ** 4\"#).unwrap(), @r#\"\n    Binary:\n      left:\n        Literal:\n          Integer: 1\n        span: \"0:0-1\"\n      op: Add\n      right:\n        Binary:\n          left:\n            Literal:\n              Integer: 2\n            span: \"0:4-5\"\n          op: Pow\n          right:\n            Binary:\n              left:\n                Binary:\n                  left:\n                    Literal:\n                      Integer: 3\n                    span: \"0:10-11\"\n                  op: Add\n                  right:\n                    Literal:\n                      Integer: 4\n                    span: \"0:14-15\"\n                span: \"0:10-15\"\n              op: Pow\n              right:\n                Literal:\n                  Integer: 4\n                span: \"0:20-21\"\n            span: \"0:9-21\"\n        span: \"0:4-21\"\n    span: \"0:0-21\"\n    \"#);\n}\n\n#[test]\nfn test_op_precedence() {\n    assert_yaml_snapshot!(parse_expr(r#\"1 + 2 - 3 - 4\"#).unwrap(), @r#\"\n    Binary:\n      left:\n        Binary:\n          left:\n            Binary:\n              left:\n                Literal:\n                  Integer: 1\n                span: \"0:0-1\"\n              op: Add\n              right:\n                Literal:\n                  Integer: 2\n                span: \"0:4-5\"\n            span: \"0:0-5\"\n          op: Sub\n          right:\n            Literal:\n              Integer: 3\n            span: \"0:8-9\"\n        span: \"0:0-9\"\n      op: Sub\n      right:\n        Literal:\n          Integer: 4\n        span: \"0:12-13\"\n    span: \"0:0-13\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"1 / (3 * 4)\"#).unwrap(), @r#\"\n    Binary:\n      left:\n        Literal:\n          Integer: 1\n        span: \"0:0-1\"\n      op: DivFloat\n      right:\n        Binary:\n          left:\n            Literal:\n              Integer: 3\n            span: \"0:5-6\"\n          op: Mul\n          right:\n            Literal:\n              Integer: 4\n            span: \"0:9-10\"\n        span: \"0:5-10\"\n    span: \"0:0-11\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"1 / 2 - 3 * 4 + 1\"#).unwrap(), @r#\"\n    Binary:\n      left:\n        Binary:\n          left:\n            Binary:\n              left:\n                Literal:\n                  Integer: 1\n                span: \"0:0-1\"\n              op: DivFloat\n              right:\n                Literal:\n                  Integer: 2\n                span: \"0:4-5\"\n            span: \"0:0-5\"\n          op: Sub\n          right:\n            Binary:\n              left:\n                Literal:\n                  Integer: 3\n                span: \"0:8-9\"\n              op: Mul\n              right:\n                Literal:\n                  Integer: 4\n                span: \"0:12-13\"\n            span: \"0:8-13\"\n        span: \"0:0-13\"\n      op: Add\n      right:\n        Literal:\n          Integer: 1\n        span: \"0:16-17\"\n    span: \"0:0-17\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"a && b || !c && d\"#).unwrap(), @r#\"\n    Binary:\n      left:\n        Binary:\n          left:\n            Ident:\n              - a\n            span: \"0:0-1\"\n          op: And\n          right:\n            Ident:\n              - b\n            span: \"0:5-6\"\n        span: \"0:0-6\"\n      op: Or\n      right:\n        Binary:\n          left:\n            Unary:\n              op: Not\n              expr:\n                Ident:\n                  - c\n                span: \"0:11-12\"\n            span: \"0:10-12\"\n          op: And\n          right:\n            Ident:\n              - d\n            span: \"0:16-17\"\n        span: \"0:10-17\"\n    span: \"0:0-17\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"a && b + c || (d e) && f\"#).unwrap(), @r#\"\n    Binary:\n      left:\n        Binary:\n          left:\n            Ident:\n              - a\n            span: \"0:0-1\"\n          op: And\n          right:\n            Binary:\n              left:\n                Ident:\n                  - b\n                span: \"0:5-6\"\n              op: Add\n              right:\n                Ident:\n                  - c\n                span: \"0:9-10\"\n            span: \"0:5-10\"\n        span: \"0:0-10\"\n      op: Or\n      right:\n        Binary:\n          left:\n            FuncCall:\n              name:\n                Ident:\n                  - d\n                span: \"0:15-16\"\n              args:\n                - Ident:\n                    - e\n                  span: \"0:17-18\"\n            span: \"0:15-18\"\n          op: And\n          right:\n            Ident:\n              - f\n            span: \"0:23-24\"\n        span: \"0:14-24\"\n    span: \"0:0-24\"\n    \"#);\n}\n\n#[test]\nfn test_inline_pipeline() {\n    assert_yaml_snapshot!(parse_expr(\"(salary | percentile 50)\").unwrap(), @r#\"\n    Pipeline:\n      exprs:\n        - Ident:\n            - salary\n          span: \"0:1-7\"\n        - FuncCall:\n            name:\n              Ident:\n                - percentile\n              span: \"0:10-20\"\n            args:\n              - Literal:\n                  Integer: 50\n                span: \"0:21-23\"\n          span: \"0:10-23\"\n    span: \"0:0-24\"\n    \"#);\n}\n\n#[test]\nfn test_dates() {\n    assert_yaml_snapshot!(parse_expr(\"@2011-02-01\").unwrap(), @r#\"\n    Literal:\n      Date: 2011-02-01\n    span: \"0:0-11\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(\"@2011-02-01T10:00\").unwrap(), @r#\"\n    Literal:\n      Timestamp: \"2011-02-01T10:00\"\n    span: \"0:0-17\"\n    \"#);\n    assert_yaml_snapshot!(parse_expr(\"@14:00\").unwrap(), @r#\"\n    Literal:\n      Time: \"14:00\"\n    span: \"0:0-6\"\n    \"#);\n    // assert_yaml_snapshot!(parse_expr(\"@2011-02-01T10:00<datetime>\").unwrap(), @\"\");\n\n    parse_expr(\"@2020-01-0\").unwrap_err();\n\n    parse_expr(\"@2020-01-011\").unwrap_err();\n\n    parse_expr(\"@2020-01-01T111\").unwrap_err();\n}\n\n#[test]\nfn test_ident_with_keywords() {\n    assert_yaml_snapshot!(parse_expr(r\"select {andrew, orion, lettuce, falsehood, null0}\").unwrap(), @r#\"\n    FuncCall:\n      name:\n        Ident:\n          - select\n        span: \"0:0-6\"\n      args:\n        - Tuple:\n            - Ident:\n                - andrew\n              span: \"0:8-14\"\n            - Ident:\n                - orion\n              span: \"0:16-21\"\n            - Ident:\n                - lettuce\n              span: \"0:23-30\"\n            - Ident:\n                - falsehood\n              span: \"0:32-41\"\n            - Ident:\n                - null0\n              span: \"0:43-48\"\n          span: \"0:7-49\"\n    span: \"0:0-49\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r\"{false}\").unwrap(), @r#\"\n    Tuple:\n      - Literal:\n          Boolean: false\n        span: \"0:1-6\"\n    span: \"0:0-7\"\n    \"#);\n}\n\n// Removed: test_case - duplicate of parser::expr::tests::test_case which uses proper EOF handling\n\n#[test]\nfn test_params() {\n    assert_yaml_snapshot!(parse_expr(r#\"$2\"#).unwrap(), @r#\"\n    Param: \"2\"\n    span: \"0:0-2\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_expr(r#\"$2_any_text\"#).unwrap(), @r#\"\n    Param: 2_any_text\n    span: \"0:0-11\"\n    \"#);\n}\n\n#[test]\nfn test_lookup_01() {\n    assert_yaml_snapshot!(parse_expr(\n    r#\"{a = {x = 2}}\"#,\n    ).unwrap(), @r#\"\n    Tuple:\n      - Tuple:\n          - Literal:\n              Integer: 2\n            span: \"0:10-11\"\n            alias: x\n        span: \"0:5-12\"\n        alias: a\n    span: \"0:0-13\"\n    \"#);\n}\n\n#[test]\nfn test_lookup_02() {\n    assert_yaml_snapshot!(parse_expr(\n    r#\"hello.*\"#,\n    ).unwrap(), @r#\"\n    Ident:\n      - hello\n      - \"*\"\n    span: \"0:0-7\"\n    \"#);\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/parser/types.rs",
    "content": "use chumsky;\nuse chumsky::input::BorrowInput;\n\nuse super::expr::ident;\nuse super::pr::*;\nuse super::*;\nuse crate::lexer::lr;\nuse crate::lexer::lr::TokenKind;\n\nuse super::ParserError;\n\npub(crate) fn type_expr<'a, I>() -> impl Parser<'a, I, Ty, ParserError<'a>> + Clone\nwhere\n    I: Input<'a, Token = lr::Token, Span = Span> + BorrowInput<'a>,\n{\n    recursive(|nested_type_expr| {\n        let basic = select_ref! {\n            lr::Token { kind: TokenKind::Ident(i), .. } if i == \"int\"=> TyKind::Primitive(PrimitiveSet::Int),\n            lr::Token { kind: TokenKind::Ident(i), .. } if i == \"float\"=> TyKind::Primitive(PrimitiveSet::Float),\n            lr::Token { kind: TokenKind::Ident(i), .. } if i == \"bool\"=> TyKind::Primitive(PrimitiveSet::Bool),\n            lr::Token { kind: TokenKind::Ident(i), .. } if i == \"text\"=> TyKind::Primitive(PrimitiveSet::Text),\n            lr::Token { kind: TokenKind::Ident(i), .. } if i == \"date\"=> TyKind::Primitive(PrimitiveSet::Date),\n            lr::Token { kind: TokenKind::Ident(i), .. } if i == \"time\"=> TyKind::Primitive(PrimitiveSet::Time),\n            lr::Token { kind: TokenKind::Ident(i), .. } if i == \"timestamp\"=> TyKind::Primitive(PrimitiveSet::Timestamp),\n        };\n\n        let ident = ident().map(TyKind::Ident);\n\n        let func = keyword(\"func\")\n            .ignore_then(\n                nested_type_expr\n                    .clone()\n                    .map(Some)\n                    .repeated()\n                    .collect()\n                    .then_ignore(select_ref! { lr::Token { kind: TokenKind::ArrowThin, .. } => () })\n                    .then(nested_type_expr.clone().map(Box::new).map(Some))\n                    .map(|(params, return_ty)| TyFunc {\n                        name_hint: None,\n                        params,\n                        return_ty,\n                    })\n                    .or_not(),\n            )\n            .map(TyKind::Function);\n\n        let tuple = {\n            use chumsky::recovery::{skip_then_retry_until, via_parser};\n\n            sequence(choice((\n                select_ref! { lr::Token { kind: TokenKind::Range { bind_right: false, bind_left: _ }, .. } => () }\n                    .to(TyTupleField::Wildcard(None)),\n                select_ref! { lr::Token { kind: TokenKind::Range { bind_right: true, bind_left: _ }, .. } => () }\n                    .ignore_then(nested_type_expr.clone().or_not())\n                    .map(TyTupleField::Wildcard),\n                ident_part()\n                    .then_ignore(ctrl('='))\n                    .or_not()\n                    .then(ctrl('*').to(None).or(nested_type_expr.clone().map(Some)))\n                    .map(|(name, ty)| TyTupleField::Single(name, ty)),\n            )))\n            .delimited_by(\n                ctrl('{'),\n                ctrl('}')\n                    .recover_with(via_parser(end()))\n                    .recover_with(skip_then_retry_until(\n                        any_ref().ignored(),\n                        ctrl('}').ignored().or(ctrl(',').ignored()).or(end()),\n                    )),\n            )\n            .try_map(|fields, span| {\n                let without_last = &fields[0..fields.len().saturating_sub(1)];\n\n                if let Some(unpack) = without_last.iter().find_map(|f| f.as_wildcard()) {\n                    let err_span = unpack.as_ref().and_then(|s| s.span).unwrap_or(span);\n                    return Err(Rich::custom(\n                        err_span,\n                        \"unpacking must come after all other fields\",\n                    ));\n                }\n\n                Ok(fields)\n            })\n            .map(TyKind::Tuple)\n            .labelled(\"tuple\")\n        };\n\n        let array = {\n            use chumsky::recovery::{skip_then_retry_until, via_parser};\n\n            nested_type_expr\n                .map(Box::new)\n                .or_not()\n                .padded_by(new_line().repeated())\n                .delimited_by(\n                    ctrl('['),\n                    ctrl(']')\n                        .recover_with(via_parser(end()))\n                        .recover_with(skip_then_retry_until(\n                            any_ref().ignored(),\n                            ctrl(']').ignored().or(ctrl(',').ignored()).or(end()),\n                        )),\n                )\n                .map(TyKind::Array)\n                .labelled(\"array\")\n        };\n\n        choice((basic, ident, func, tuple, array))\n            .map_with(|kind, extra| TyKind::into_ty(kind, extra.span()))\n\n        // exclude\n        // term.clone()\n        //     .then(ctrl('-').ignore_then(term).repeated())\n        //     .foldl(|left, right| {\n        //         let left_span = left.span.as_ref().unwrap();\n        //         let right_span = right.span.as_ref().unwrap();\n        //         let span = Span {\n        //             start: left_span.start,\n        //             end: right_span.end,\n        //             source_id: left_span.source_id,\n        //         };\n\n        //         let kind = TyKind::Exclude {\n        //             base: Box::new(left),\n        //             except: Box::new(right),\n        //         };\n        //         into_ty(kind, span)\n        //     });\n    })\n    .boxed()\n    .labelled(\"type expression\")\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/snapshots/prqlc_parser__test__pipeline_parse_tree.snap",
    "content": "---\nsource: prqlc/prqlc-parser/src/test.rs\nexpression: \"parse_source(r#\\\"\\nfrom employees\\nfilter country == \\\"USA\\\"                      # Each line transforms the previous result.\\nderive {                                     # This adds columns / variables.\\n  gross_salary = salary + payroll_tax,\\n  gross_cost = gross_salary + benefits_cost  # Variables can use other variables.\\n}\\nfilter gross_cost > 0\\ngroup {title, country} (                     # For each group use a nested pipeline\\n  aggregate {                                # Aggregate each group to a single row\\n    average salary,\\n    average gross_salary,\\n    sum salary,\\n    sum gross_salary,\\n    average gross_cost,\\n    sum_gross_cost = sum gross_cost,\\n    ct = count salary,\\n  }\\n)\\nsort sum_gross_cost\\nfilter ct > 200\\ntake 20\\n        \\\"#).unwrap()\"\n---\n- VarDef:\n    kind: Main\n    name: main\n    value:\n      Pipeline:\n        exprs:\n          - FuncCall:\n              name:\n                Ident:\n                  - from\n                span: \"0:1-5\"\n              args:\n                - Ident:\n                    - employees\n                  span: \"0:6-15\"\n            span: \"0:1-15\"\n          - FuncCall:\n              name:\n                Ident:\n                  - filter\n                span: \"0:16-22\"\n              args:\n                - Binary:\n                    left:\n                      Ident:\n                        - country\n                      span: \"0:23-30\"\n                    op: Eq\n                    right:\n                      Literal:\n                        String: USA\n                      span: \"0:34-39\"\n                  span: \"0:23-39\"\n            span: \"0:16-39\"\n          - FuncCall:\n              name:\n                Ident:\n                  - derive\n                span: \"0:105-111\"\n              args:\n                - Tuple:\n                    - Binary:\n                        left:\n                          Ident:\n                            - salary\n                          span: \"0:200-206\"\n                        op: Add\n                        right:\n                          Ident:\n                            - payroll_tax\n                          span: \"0:209-220\"\n                      span: \"0:200-220\"\n                      alias: gross_salary\n                    - Binary:\n                        left:\n                          Ident:\n                            - gross_salary\n                          span: \"0:237-249\"\n                        op: Add\n                        right:\n                          Ident:\n                            - benefits_cost\n                          span: \"0:252-265\"\n                      span: \"0:237-265\"\n                      alias: gross_cost\n                  span: \"0:112-305\"\n            span: \"0:105-305\"\n          - FuncCall:\n              name:\n                Ident:\n                  - filter\n                span: \"0:306-312\"\n              args:\n                - Binary:\n                    left:\n                      Ident:\n                        - gross_cost\n                      span: \"0:313-323\"\n                    op: Gt\n                    right:\n                      Literal:\n                        Integer: 0\n                      span: \"0:326-327\"\n                  span: \"0:313-327\"\n            span: \"0:306-327\"\n          - FuncCall:\n              name:\n                Ident:\n                  - group\n                span: \"0:328-333\"\n              args:\n                - Tuple:\n                    - Ident:\n                        - title\n                      span: \"0:335-340\"\n                    - Ident:\n                        - country\n                      span: \"0:342-349\"\n                  span: \"0:334-350\"\n                - FuncCall:\n                    name:\n                      Ident:\n                        - aggregate\n                      span: \"0:414-423\"\n                    args:\n                      - Tuple:\n                          - FuncCall:\n                              name:\n                                Ident:\n                                  - average\n                                span: \"0:500-507\"\n                              args:\n                                - Ident:\n                                    - salary\n                                  span: \"0:508-514\"\n                            span: \"0:500-514\"\n                          - FuncCall:\n                              name:\n                                Ident:\n                                  - average\n                                span: \"0:520-527\"\n                              args:\n                                - Ident:\n                                    - gross_salary\n                                  span: \"0:528-540\"\n                            span: \"0:520-540\"\n                          - FuncCall:\n                              name:\n                                Ident:\n                                  - sum\n                                span: \"0:546-549\"\n                              args:\n                                - Ident:\n                                    - salary\n                                  span: \"0:550-556\"\n                            span: \"0:546-556\"\n                          - FuncCall:\n                              name:\n                                Ident:\n                                  - sum\n                                span: \"0:562-565\"\n                              args:\n                                - Ident:\n                                    - gross_salary\n                                  span: \"0:566-578\"\n                            span: \"0:562-578\"\n                          - FuncCall:\n                              name:\n                                Ident:\n                                  - average\n                                span: \"0:584-591\"\n                              args:\n                                - Ident:\n                                    - gross_cost\n                                  span: \"0:592-602\"\n                            span: \"0:584-602\"\n                          - FuncCall:\n                              name:\n                                Ident:\n                                  - sum\n                                span: \"0:625-628\"\n                              args:\n                                - Ident:\n                                    - gross_cost\n                                  span: \"0:629-639\"\n                            span: \"0:625-639\"\n                            alias: sum_gross_cost\n                          - FuncCall:\n                              name:\n                                Ident:\n                                  - count\n                                span: \"0:650-655\"\n                              args:\n                                - Ident:\n                                    - salary\n                                  span: \"0:656-662\"\n                            span: \"0:650-662\"\n                            alias: ct\n                        span: \"0:424-667\"\n                  span: \"0:414-667\"\n            span: \"0:328-669\"\n          - FuncCall:\n              name:\n                Ident:\n                  - sort\n                span: \"0:670-674\"\n              args:\n                - Ident:\n                    - sum_gross_cost\n                  span: \"0:675-689\"\n            span: \"0:670-689\"\n          - FuncCall:\n              name:\n                Ident:\n                  - filter\n                span: \"0:690-696\"\n              args:\n                - Binary:\n                    left:\n                      Ident:\n                        - ct\n                      span: \"0:697-699\"\n                    op: Gt\n                    right:\n                      Literal:\n                        Integer: 200\n                      span: \"0:702-705\"\n                  span: \"0:697-705\"\n            span: \"0:690-705\"\n          - FuncCall:\n              name:\n                Ident:\n                  - take\n                span: \"0:706-710\"\n              args:\n                - Literal:\n                    Integer: 20\n                  span: \"0:711-713\"\n            span: \"0:706-713\"\n      span: \"0:1-713\"\n  span: \"0:0-713\"\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/span.rs",
    "content": "use std::fmt::{self, Debug, Formatter};\nuse std::ops::{Add, Range, Sub};\n\nuse chumsky;\nuse schemars::JsonSchema;\nuse serde::de::Visitor;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Clone, PartialEq, Eq, Copy, JsonSchema)]\npub struct Span {\n    pub start: usize,\n    pub end: usize,\n\n    /// A key representing the path of the source. Value is stored in prqlc's SourceTree::source_ids.\n    pub source_id: u16,\n}\n\n// Implement Chumsky's Span trait for our custom Span\nimpl chumsky::span::Span for Span {\n    type Context = u16; // source_id\n    type Offset = usize;\n\n    fn new(context: Self::Context, range: Range<Self::Offset>) -> Self {\n        Span {\n            start: range.start,\n            end: range.end,\n            source_id: context,\n        }\n    }\n\n    fn context(&self) -> Self::Context {\n        self.source_id\n    }\n\n    fn start(&self) -> Self::Offset {\n        self.start\n    }\n\n    fn end(&self) -> Self::Offset {\n        self.end\n    }\n\n    fn to_end(&self) -> Self {\n        Span {\n            start: self.end,\n            end: self.end,\n            source_id: self.source_id,\n        }\n    }\n}\n\nimpl From<Span> for Range<usize> {\n    fn from(a: Span) -> Self {\n        a.start..a.end\n    }\n}\n\nimpl Debug for Span {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}:{}-{}\", self.source_id, self.start, self.end)\n    }\n}\n\nimpl Serialize for Span {\n    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n        let str = format!(\"{self:?}\");\n        serializer.serialize_str(&str)\n    }\n}\n\nimpl PartialOrd for Span {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        // We could expand this to compare source_id too, starting with minimum surprise\n        match other.source_id.partial_cmp(&self.source_id) {\n            Some(std::cmp::Ordering::Equal) => {\n                debug_assert!((self.start <= other.start) == (self.end <= other.end));\n                self.start.partial_cmp(&other.start)\n            }\n            _ => None,\n        }\n    }\n}\n\nimpl<'de> Deserialize<'de> for Span {\n    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        struct SpanVisitor {}\n\n        impl Visitor<'_> for SpanVisitor {\n            type Value = Span;\n\n            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {\n                write!(f, \"A span string of form `file_id:x-y`\")\n            }\n\n            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>\n            where\n                E: serde::de::Error,\n            {\n                use serde::de;\n\n                if let Some((file_id, char_span)) = v.split_once(':') {\n                    let file_id = file_id\n                        .parse::<u16>()\n                        .map_err(|e| de::Error::custom(e.to_string()))?;\n\n                    if let Some((start, end)) = char_span.split_once('-') {\n                        let start = start\n                            .parse::<usize>()\n                            .map_err(|e| de::Error::custom(e.to_string()))?;\n                        let end = end\n                            .parse::<usize>()\n                            .map_err(|e| de::Error::custom(e.to_string()))?;\n\n                        return Ok(Span {\n                            start,\n                            end,\n                            source_id: file_id,\n                        });\n                    }\n                }\n\n                Err(de::Error::custom(\"malformed span\"))\n            }\n\n            fn visit_string<E>(self, v: String) -> std::result::Result<Self::Value, E>\n            where\n                E: serde::de::Error,\n            {\n                self.visit_str(&v)\n            }\n        }\n\n        deserializer.deserialize_string(SpanVisitor {})\n    }\n}\n\nimpl Add<usize> for Span {\n    type Output = Span;\n\n    fn add(self, rhs: usize) -> Span {\n        Self {\n            start: self.start + rhs,\n            end: self.end + rhs,\n            source_id: self.source_id,\n        }\n    }\n}\n\nimpl Sub<usize> for Span {\n    type Output = Span;\n\n    fn sub(self, rhs: usize) -> Span {\n        Self {\n            start: self.start - rhs,\n            end: self.end - rhs,\n            source_id: self.source_id,\n        }\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_span_serde() {\n        let span = Span {\n            start: 12,\n            end: 15,\n            source_id: 45,\n        };\n        let span_serialized = serde_json::to_string(&span).unwrap();\n        insta::assert_snapshot!(span_serialized, @r#\"\"45:12-15\"\"#);\n        let span_deserialized: Span = serde_json::from_str(&span_serialized).unwrap();\n        assert_eq!(span_deserialized, span);\n    }\n\n    #[test]\n    fn test_span_partial_cmp() {\n        let span1 = Span {\n            start: 10,\n            end: 20,\n            source_id: 1,\n        };\n        let span2 = Span {\n            start: 15,\n            end: 25,\n            source_id: 1,\n        };\n        let span3 = Span {\n            start: 5,\n            end: 15,\n            source_id: 2,\n        };\n\n        // span1 and span2 have the same source_id, so their start values are compared\n        assert_eq!(span1.partial_cmp(&span2), Some(std::cmp::Ordering::Less));\n        assert_eq!(span2.partial_cmp(&span1), Some(std::cmp::Ordering::Greater));\n\n        // span1 and span3 have different source_id, so their source_id values are compared\n        assert_eq!(span1.partial_cmp(&span3), None);\n        assert_eq!(span3.partial_cmp(&span1), None);\n    }\n}\n"
  },
  {
    "path": "prqlc/prqlc-parser/src/test.rs",
    "content": "use chumsky::prelude::*;\nuse chumsky::span::SimpleSpan;\nuse insta::{assert_debug_snapshot, assert_yaml_snapshot};\n\nuse crate::error::Error;\nuse crate::parser::pr::Stmt;\nuse crate::parser::stmt;\nuse crate::span::Span;\n\n/// Parse into statements\npub(crate) fn parse_source(source: &str) -> Result<Vec<Stmt>, Vec<Error>> {\n    let tokens = crate::lexer::lex_source(source)?;\n\n    // Filter out comments\n    let semantic_tokens: Vec<_> = tokens\n        .0\n        .into_iter()\n        .filter(|token| {\n            !matches!(\n                token.kind,\n                crate::lexer::lr::TokenKind::Comment(_) | crate::lexer::lr::TokenKind::LineWrap(_)\n            )\n        })\n        .collect();\n\n    let input = semantic_tokens\n        .as_slice()\n        .map_span(|simple_span: SimpleSpan| {\n            let start_idx = simple_span.start();\n            let end_idx = simple_span.end();\n\n            let start = semantic_tokens\n                .get(start_idx)\n                .map(|t| t.span.start)\n                .unwrap_or(0);\n            let end = semantic_tokens\n                .get(end_idx.saturating_sub(1))\n                .map(|t| t.span.end)\n                .unwrap_or(start);\n\n            Span {\n                start,\n                end,\n                source_id: 0,\n            }\n        });\n\n    let parse_result = stmt::source().parse(input);\n    let (ast, parse_errors) = parse_result.into_output_errors();\n\n    if !parse_errors.is_empty() {\n        log::info!(\"ast: {ast:?}\");\n        return Err(parse_errors.into_iter().map(|e| e.into()).collect());\n    }\n    Ok(ast.unwrap())\n}\n\n#[test]\nfn test_error_unicode_string() {\n    // Test various unicode strings successfully parse errors. We were\n    // getting loops in the lexer before.\n    parse_source(\"s’ \").unwrap_err();\n    parse_source(\"s’\").unwrap_err();\n    parse_source(\" s’\").unwrap_err();\n    parse_source(\" ’ s\").unwrap_err();\n    parse_source(\"’s\").unwrap_err();\n    parse_source(\"👍 s’\").unwrap_err();\n\n    let source = \"Mississippi has four S’s and four I’s.\";\n\n    // LEXER output for comparison (what the lexer sees):\n    assert_debug_snapshot!(crate::lexer::lex_source(source).unwrap_err(), @r#\"\n    [\n        Error {\n            kind: Error,\n            span: Some(\n                0:22-23,\n            ),\n            reason: Unexpected {\n                found: \"'’'\",\n            },\n            hints: [],\n            code: None,\n        },\n    ]\n    \"#);\n\n    // PARSER output (what happens after lexing):\n    assert_debug_snapshot!(parse_source(source).unwrap_err(), @r#\"\n    [\n        Error {\n            kind: Error,\n            span: Some(\n                0:22-23,\n            ),\n            reason: Unexpected {\n                found: \"'’'\",\n            },\n            hints: [],\n            code: None,\n        },\n    ]\n    \"#);\n}\n\n#[test]\nfn test_error_unexpected() {\n    assert_debug_snapshot!(parse_source(\"Answer: T-H-A-T!\").unwrap_err(), @r#\"\n    [\n        Error {\n            kind: Error,\n            span: Some(\n                0:15-16,\n            ),\n            reason: Expected {\n                who: None,\n                expected: \"something else\",\n                found: \"!\",\n            },\n            hints: [],\n            code: None,\n        },\n    ]\n    \"#);\n}\n\n#[test]\nfn test_pipeline_parse_tree() {\n    assert_yaml_snapshot!(parse_source(\n        r#\"\nfrom employees\nfilter country == \"USA\"                      # Each line transforms the previous result.\nderive {                                     # This adds columns / variables.\n  gross_salary = salary + payroll_tax,\n  gross_cost = gross_salary + benefits_cost  # Variables can use other variables.\n}\nfilter gross_cost > 0\ngroup {title, country} (                     # For each group use a nested pipeline\n  aggregate {                                # Aggregate each group to a single row\n    average salary,\n    average gross_salary,\n    sum salary,\n    sum gross_salary,\n    average gross_cost,\n    sum_gross_cost = sum gross_cost,\n    ct = count salary,\n  }\n)\nsort sum_gross_cost\nfilter ct > 200\ntake 20\n        \"#\n    )\n    .unwrap());\n}\n\n#[test]\nfn test_take() {\n    parse_source(\"take 10\").unwrap();\n\n    assert_yaml_snapshot!(parse_source(r#\"take 10\"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - take\n              span: \"0:0-4\"\n            args:\n              - Literal:\n                  Integer: 10\n                span: \"0:5-7\"\n          span: \"0:0-7\"\n      span: \"0:0-7\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(r#\"take ..10\"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - take\n              span: \"0:0-4\"\n            args:\n              - Range:\n                  start: ~\n                  end:\n                    Literal:\n                      Integer: 10\n                    span: \"0:7-9\"\n                span: \"0:4-9\"\n          span: \"0:0-9\"\n      span: \"0:0-9\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(r#\"take 1..10\"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - take\n              span: \"0:0-4\"\n            args:\n              - Range:\n                  start:\n                    Literal:\n                      Integer: 1\n                    span: \"0:5-6\"\n                  end:\n                    Literal:\n                      Integer: 10\n                    span: \"0:8-10\"\n                span: \"0:5-10\"\n          span: \"0:0-10\"\n      span: \"0:0-10\"\n    \"#);\n}\n\n#[test]\nfn test_filter() {\n    assert_yaml_snapshot!(\n            parse_source(r#\"filter country == \"USA\"\"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - filter\n              span: \"0:0-6\"\n            args:\n              - Binary:\n                  left:\n                    Ident:\n                      - country\n                    span: \"0:7-14\"\n                  op: Eq\n                  right:\n                    Literal:\n                      String: USA\n                    span: \"0:18-23\"\n                span: \"0:7-23\"\n          span: \"0:0-23\"\n      span: \"0:0-23\"\n    \"#);\n\n    assert_yaml_snapshot!(\n        parse_source(r#\"filter (text.upper country) == \"USA\"\"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - filter\n              span: \"0:0-6\"\n            args:\n              - Binary:\n                  left:\n                    FuncCall:\n                      name:\n                        Ident:\n                          - text\n                          - upper\n                        span: \"0:8-18\"\n                      args:\n                        - Ident:\n                            - country\n                          span: \"0:19-26\"\n                    span: \"0:8-26\"\n                  op: Eq\n                  right:\n                    Literal:\n                      String: USA\n                    span: \"0:31-36\"\n                span: \"0:7-36\"\n          span: \"0:0-36\"\n      span: \"0:0-36\"\n    \"#\n    );\n}\n\n#[test]\nfn test_aggregate() {\n    let aggregate = parse_source(\n        r\"group {title} (\n                aggregate {sum salary, count}\n              )\",\n    )\n    .unwrap();\n    assert_yaml_snapshot!(\n            aggregate, @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - group\n              span: \"0:0-5\"\n            args:\n              - Tuple:\n                  - Ident:\n                      - title\n                    span: \"0:7-12\"\n                span: \"0:6-13\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - aggregate\n                    span: \"0:32-41\"\n                  args:\n                    - Tuple:\n                        - FuncCall:\n                            name:\n                              Ident:\n                                - sum\n                              span: \"0:43-46\"\n                            args:\n                              - Ident:\n                                  - salary\n                                span: \"0:47-53\"\n                          span: \"0:43-53\"\n                        - Ident:\n                            - count\n                          span: \"0:55-60\"\n                      span: \"0:42-61\"\n                span: \"0:32-61\"\n          span: \"0:0-77\"\n      span: \"0:0-77\"\n    \"#);\n    let aggregate = parse_source(\n        r\"group {title} (\n                aggregate {sum salary}\n              )\",\n    )\n    .unwrap();\n    assert_yaml_snapshot!(\n            aggregate, @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - group\n              span: \"0:0-5\"\n            args:\n              - Tuple:\n                  - Ident:\n                      - title\n                    span: \"0:7-12\"\n                span: \"0:6-13\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - aggregate\n                    span: \"0:32-41\"\n                  args:\n                    - Tuple:\n                        - FuncCall:\n                            name:\n                              Ident:\n                                - sum\n                              span: \"0:43-46\"\n                            args:\n                              - Ident:\n                                  - salary\n                                span: \"0:47-53\"\n                          span: \"0:43-53\"\n                      span: \"0:42-54\"\n                span: \"0:32-54\"\n          span: \"0:0-70\"\n      span: \"0:0-70\"\n    \"#);\n}\n\n#[test]\nfn test_basic_exprs() {\n    // Currently not putting comments in our parse tree, so this is blank.\n    assert_yaml_snapshot!(parse_source(\n            r#\"# this is a comment\n        select a\"#\n        ).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - select\n              span: \"0:28-34\"\n            args:\n              - Ident:\n                  - a\n                span: \"0:35-36\"\n          span: \"0:28-36\"\n      span: \"0:0-36\"\n    \"#);\n}\n\n#[test]\nfn test_function() {\n    assert_yaml_snapshot!(parse_source(\"let plus_one = x ->  x + 1\\n\").unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: plus_one\n        value:\n          Func:\n            return_ty: ~\n            body:\n              Binary:\n                left:\n                  Ident:\n                    - x\n                  span: \"0:21-22\"\n                op: Add\n                right:\n                  Literal:\n                    Integer: 1\n                  span: \"0:25-26\"\n              span: \"0:21-26\"\n            params:\n              - name: x\n                default_value: ~\n            named_params: []\n          span: \"0:15-26\"\n      span: \"0:0-26\"\n    \"#);\n    assert_yaml_snapshot!(parse_source(\"let identity = x ->  x\\n\").unwrap()\n        , @r#\"\n    - VarDef:\n        kind: Let\n        name: identity\n        value:\n          Func:\n            return_ty: ~\n            body:\n              Ident:\n                - x\n              span: \"0:21-22\"\n            params:\n              - name: x\n                default_value: ~\n            named_params: []\n          span: \"0:15-22\"\n      span: \"0:0-22\"\n    \"#);\n    assert_yaml_snapshot!(parse_source(\"let plus_one = x ->  (x + 1)\\n\").unwrap()\n        , @r#\"\n    - VarDef:\n        kind: Let\n        name: plus_one\n        value:\n          Func:\n            return_ty: ~\n            body:\n              Binary:\n                left:\n                  Ident:\n                    - x\n                  span: \"0:22-23\"\n                op: Add\n                right:\n                  Literal:\n                    Integer: 1\n                  span: \"0:26-27\"\n              span: \"0:21-28\"\n            params:\n              - name: x\n                default_value: ~\n            named_params: []\n          span: \"0:15-28\"\n      span: \"0:0-28\"\n    \"#);\n    assert_yaml_snapshot!(parse_source(\"let plus_one = x ->  x + 1\\n\").unwrap()\n        , @r#\"\n    - VarDef:\n        kind: Let\n        name: plus_one\n        value:\n          Func:\n            return_ty: ~\n            body:\n              Binary:\n                left:\n                  Ident:\n                    - x\n                  span: \"0:21-22\"\n                op: Add\n                right:\n                  Literal:\n                    Integer: 1\n                  span: \"0:25-26\"\n              span: \"0:21-26\"\n            params:\n              - name: x\n                default_value: ~\n            named_params: []\n          span: \"0:15-26\"\n      span: \"0:0-26\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(\"let foo = x -> some_func (foo bar + 1) (plax) - baz\\n\").unwrap()\n        , @r#\"\n    - VarDef:\n        kind: Let\n        name: foo\n        value:\n          Func:\n            return_ty: ~\n            body:\n              FuncCall:\n                name:\n                  Ident:\n                    - some_func\n                  span: \"0:15-24\"\n                args:\n                  - FuncCall:\n                      name:\n                        Ident:\n                          - foo\n                        span: \"0:26-29\"\n                      args:\n                        - Binary:\n                            left:\n                              Ident:\n                                - bar\n                              span: \"0:30-33\"\n                            op: Add\n                            right:\n                              Literal:\n                                Integer: 1\n                              span: \"0:36-37\"\n                          span: \"0:30-37\"\n                    span: \"0:26-37\"\n                  - Binary:\n                      left:\n                        Ident:\n                          - plax\n                        span: \"0:40-44\"\n                      op: Sub\n                      right:\n                        Ident:\n                          - baz\n                        span: \"0:48-51\"\n                    span: \"0:39-51\"\n              span: \"0:15-51\"\n            params:\n              - name: x\n                default_value: ~\n            named_params: []\n          span: \"0:10-51\"\n      span: \"0:0-51\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(\"func return_constant ->  42\\n\").unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Func:\n            return_ty: ~\n            body:\n              Literal:\n                Integer: 42\n              span: \"0:25-27\"\n            params:\n              - name: return_constant\n                default_value: ~\n            named_params: []\n          span: \"0:0-27\"\n      span: \"0:0-27\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(r#\"let count = X -> s\"SUM({X})\"\n        \"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: count\n        value:\n          Func:\n            return_ty: ~\n            body:\n              SString:\n                - String: SUM(\n                - Expr:\n                    expr:\n                      Ident:\n                        - X\n                      span: \"0:24-25\"\n                    format: ~\n                - String: )\n              span: \"0:17-28\"\n            params:\n              - name: X\n                default_value: ~\n            named_params: []\n          span: \"0:12-28\"\n      span: \"0:0-28\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(\n            r#\"\n            let lag_day = x ->  (\n                window x\n                by sec_id\n                sort date\n                lag 1\n            )\n        \"#\n        )\n        .unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: lag_day\n        value:\n          Func:\n            return_ty: ~\n            body:\n              Pipeline:\n                exprs:\n                  - FuncCall:\n                      name:\n                        Ident:\n                          - window\n                        span: \"0:51-57\"\n                      args:\n                        - Ident:\n                            - x\n                          span: \"0:58-59\"\n                    span: \"0:51-59\"\n                  - FuncCall:\n                      name:\n                        Ident:\n                          - by\n                        span: \"0:76-78\"\n                      args:\n                        - Ident:\n                            - sec_id\n                          span: \"0:79-85\"\n                    span: \"0:76-85\"\n                  - FuncCall:\n                      name:\n                        Ident:\n                          - sort\n                        span: \"0:102-106\"\n                      args:\n                        - Ident:\n                            - date\n                          span: \"0:107-111\"\n                    span: \"0:102-111\"\n                  - FuncCall:\n                      name:\n                        Ident:\n                          - lag\n                        span: \"0:128-131\"\n                      args:\n                        - Literal:\n                            Integer: 1\n                          span: \"0:132-133\"\n                    span: \"0:128-133\"\n              span: \"0:33-147\"\n            params:\n              - name: x\n                default_value: ~\n            named_params: []\n          span: \"0:27-147\"\n      span: \"0:0-147\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(\"let add = x to:a ->  x + to\\n\").unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: add\n        value:\n          Func:\n            return_ty: ~\n            body:\n              Binary:\n                left:\n                  Ident:\n                    - x\n                  span: \"0:21-22\"\n                op: Add\n                right:\n                  Ident:\n                    - to\n                  span: \"0:25-27\"\n              span: \"0:21-27\"\n            params:\n              - name: x\n                default_value: ~\n            named_params:\n              - name: to\n                default_value:\n                  Ident:\n                    - a\n                  span: \"0:15-16\"\n          span: \"0:10-27\"\n      span: \"0:0-27\"\n    \"#);\n}\n\n#[test]\nfn test_var_def() {\n    assert_yaml_snapshot!(parse_source(\n            \"let newest_employees = (from employees)\"\n        ).unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: newest_employees\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - from\n              span: \"0:24-28\"\n            args:\n              - Ident:\n                  - employees\n                span: \"0:29-38\"\n          span: \"0:23-39\"\n      span: \"0:0-39\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(\n            r#\"\n        let newest_employees = (\n          from employees\n          group country (\n            aggregate {\n                average_country_salary = average salary\n            }\n          )\n          sort tenure\n          take 50\n        )\"#.trim()).unwrap(),\n         @r#\"\n    - VarDef:\n        kind: Let\n        name: newest_employees\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:35-39\"\n                  args:\n                    - Ident:\n                        - employees\n                      span: \"0:40-49\"\n                span: \"0:35-49\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - group\n                    span: \"0:60-65\"\n                  args:\n                    - Ident:\n                        - country\n                      span: \"0:66-73\"\n                    - FuncCall:\n                        name:\n                          Ident:\n                            - aggregate\n                          span: \"0:88-97\"\n                        args:\n                          - Tuple:\n                              - FuncCall:\n                                  name:\n                                    Ident:\n                                      - average\n                                    span: \"0:141-148\"\n                                  args:\n                                    - Ident:\n                                        - salary\n                                      span: \"0:149-155\"\n                                span: \"0:141-155\"\n                                alias: average_country_salary\n                            span: \"0:98-169\"\n                      span: \"0:88-169\"\n                span: \"0:60-181\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - sort\n                    span: \"0:192-196\"\n                  args:\n                    - Ident:\n                        - tenure\n                      span: \"0:197-203\"\n                span: \"0:192-203\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - take\n                    span: \"0:214-218\"\n                  args:\n                    - Literal:\n                        Integer: 50\n                      span: \"0:219-221\"\n                span: \"0:214-221\"\n          span: \"0:23-231\"\n      span: \"0:0-231\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(r#\"\n            let e = s\"SELECT * FROM employees\"\n            \"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: e\n        value:\n          SString:\n            - String: SELECT * FROM employees\n          span: \"0:21-47\"\n      span: \"0:0-47\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(\n          \"let x = (\n\n            from x_table\n\n            select only_in_x = foo\n\n          )\n\n          from x\"\n        ).unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: x\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:23-27\"\n                  args:\n                    - Ident:\n                        - x_table\n                      span: \"0:28-35\"\n                span: \"0:23-35\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - select\n                    span: \"0:49-55\"\n                  args:\n                    - Ident:\n                        - foo\n                      span: \"0:68-71\"\n                      alias: only_in_x\n                span: \"0:49-71\"\n          span: \"0:8-84\"\n      span: \"0:0-84\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - from\n              span: \"0:96-100\"\n            args:\n              - Ident:\n                  - x\n                span: \"0:101-102\"\n          span: \"0:96-102\"\n      span: \"0:84-102\"\n    \"#);\n}\n\n#[test]\nfn test_inline_pipeline() {\n    assert_yaml_snapshot!(parse_source(\"let median = x -> (x | percentile 50)\\n\").unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: median\n        value:\n          Func:\n            return_ty: ~\n            body:\n              Pipeline:\n                exprs:\n                  - Ident:\n                      - x\n                    span: \"0:19-20\"\n                  - FuncCall:\n                      name:\n                        Ident:\n                          - percentile\n                        span: \"0:23-33\"\n                      args:\n                        - Literal:\n                            Integer: 50\n                          span: \"0:34-36\"\n                    span: \"0:23-36\"\n              span: \"0:18-37\"\n            params:\n              - name: x\n                default_value: ~\n            named_params: []\n          span: \"0:13-37\"\n      span: \"0:0-37\"\n    \"#);\n}\n\n#[test]\nfn test_sql_parameters() {\n    assert_yaml_snapshot!(parse_source(r#\"\n        from mytable\n        filter {\n          first_name == $1,\n          last_name == $2.name\n        }\n        \"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:9-13\"\n                  args:\n                    - Ident:\n                        - mytable\n                      span: \"0:14-21\"\n                span: \"0:9-21\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - filter\n                    span: \"0:30-36\"\n                  args:\n                    - Tuple:\n                        - Binary:\n                            left:\n                              Ident:\n                                - first_name\n                              span: \"0:49-59\"\n                            op: Eq\n                            right:\n                              Param: \"1\"\n                              span: \"0:63-65\"\n                          span: \"0:49-65\"\n                        - Binary:\n                            left:\n                              Ident:\n                                - last_name\n                              span: \"0:77-86\"\n                            op: Eq\n                            right:\n                              Param: 2.name\n                              span: \"0:90-97\"\n                          span: \"0:77-97\"\n                      span: \"0:37-107\"\n                span: \"0:30-107\"\n          span: \"0:9-107\"\n      span: \"0:0-107\"\n    \"#);\n}\n\n#[test]\nfn test_tab_characters() {\n    // #284\n    parse_source(\n        \"from c_invoice\njoin doc:c_doctype (==c_invoice_id)\nselect [\n\\tinvoice_no,\n\\tdocstatus\n]\",\n    )\n    .unwrap();\n}\n\n#[test]\nfn test_backticks() {\n    let prql = \"\nfrom `a/*.parquet`\naggregate {max c}\njoin `schema.table` (==id)\njoin `my-proj.dataset.table`\njoin `my-proj`.`dataset`.`table`\n\";\n\n    assert_yaml_snapshot!(parse_source(prql).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:1-5\"\n                  args:\n                    - Ident:\n                        - a/*.parquet\n                      span: \"0:6-19\"\n                span: \"0:1-19\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - aggregate\n                    span: \"0:20-29\"\n                  args:\n                    - Tuple:\n                        - FuncCall:\n                            name:\n                              Ident:\n                                - max\n                              span: \"0:31-34\"\n                            args:\n                              - Ident:\n                                  - c\n                                span: \"0:35-36\"\n                          span: \"0:31-36\"\n                      span: \"0:30-37\"\n                span: \"0:20-37\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - join\n                    span: \"0:38-42\"\n                  args:\n                    - Ident:\n                        - schema.table\n                      span: \"0:43-57\"\n                    - Unary:\n                        op: EqSelf\n                        expr:\n                          Ident:\n                            - id\n                          span: \"0:61-63\"\n                      span: \"0:59-63\"\n                span: \"0:38-64\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - join\n                    span: \"0:65-69\"\n                  args:\n                    - Ident:\n                        - my-proj.dataset.table\n                      span: \"0:70-93\"\n                span: \"0:65-93\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - join\n                    span: \"0:94-98\"\n                  args:\n                    - Ident:\n                        - my-proj\n                        - dataset\n                        - table\n                      span: \"0:99-126\"\n                span: \"0:94-126\"\n          span: \"0:1-126\"\n      span: \"0:0-126\"\n    \"#);\n}\n\n#[test]\nfn test_sort() {\n    assert_yaml_snapshot!(parse_source(\"\n        from invoices\n        sort issued_at\n        sort (-issued_at)\n        sort {issued_at}\n        sort {-issued_at}\n        sort {issued_at, -amount, +num_of_articles}\n        \").unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:9-13\"\n                  args:\n                    - Ident:\n                        - invoices\n                      span: \"0:14-22\"\n                span: \"0:9-22\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - sort\n                    span: \"0:31-35\"\n                  args:\n                    - Ident:\n                        - issued_at\n                      span: \"0:36-45\"\n                span: \"0:31-45\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - sort\n                    span: \"0:54-58\"\n                  args:\n                    - Unary:\n                        op: Neg\n                        expr:\n                          Ident:\n                            - issued_at\n                          span: \"0:61-70\"\n                      span: \"0:60-70\"\n                span: \"0:54-71\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - sort\n                    span: \"0:80-84\"\n                  args:\n                    - Tuple:\n                        - Ident:\n                            - issued_at\n                          span: \"0:86-95\"\n                      span: \"0:85-96\"\n                span: \"0:80-96\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - sort\n                    span: \"0:105-109\"\n                  args:\n                    - Tuple:\n                        - Unary:\n                            op: Neg\n                            expr:\n                              Ident:\n                                - issued_at\n                              span: \"0:112-121\"\n                          span: \"0:111-121\"\n                      span: \"0:110-122\"\n                span: \"0:105-122\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - sort\n                    span: \"0:131-135\"\n                  args:\n                    - Tuple:\n                        - Ident:\n                            - issued_at\n                          span: \"0:137-146\"\n                        - Unary:\n                            op: Neg\n                            expr:\n                              Ident:\n                                - amount\n                              span: \"0:149-155\"\n                          span: \"0:148-155\"\n                        - Unary:\n                            op: Add\n                            expr:\n                              Ident:\n                                - num_of_articles\n                              span: \"0:158-173\"\n                          span: \"0:157-173\"\n                      span: \"0:136-174\"\n                span: \"0:131-174\"\n          span: \"0:9-174\"\n      span: \"0:0-174\"\n    \"#);\n}\n\n#[test]\nfn test_dates() {\n    assert_yaml_snapshot!(parse_source(\"\n        from employees\n        derive {age_plus_two_years = (age + 2years)}\n        \").unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:9-13\"\n                  args:\n                    - Ident:\n                        - employees\n                      span: \"0:14-23\"\n                span: \"0:9-23\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - derive\n                    span: \"0:32-38\"\n                  args:\n                    - Tuple:\n                        - Binary:\n                            left:\n                              Ident:\n                                - age\n                              span: \"0:62-65\"\n                            op: Add\n                            right:\n                              Literal:\n                                ValueAndUnit:\n                                  n: 2\n                                  unit: years\n                              span: \"0:68-74\"\n                          span: \"0:61-75\"\n                          alias: age_plus_two_years\n                      span: \"0:39-76\"\n                span: \"0:32-76\"\n          span: \"0:9-76\"\n      span: \"0:0-76\"\n    \"#);\n}\n\n#[test]\nfn test_multiline_string() {\n    assert_yaml_snapshot!(parse_source(r##\"\n        derive x = r\"r-string test\"\n        \"##).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - derive\n              span: \"0:9-15\"\n            args:\n              - Literal:\n                  RawString: r-string test\n                span: \"0:20-36\"\n                alias: x\n          span: \"0:9-36\"\n      span: \"0:0-36\"\n    \"# )\n}\n\n#[test]\nfn test_empty_lines() {\n    // The span of the Pipeline shouldn't include the empty lines; the VarDef\n    // should have a larger span\n    assert_yaml_snapshot!(parse_source(r#\"\nfrom artists\nderive x = 5\n\n \n\n\"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:1-5\"\n                  args:\n                    - Ident:\n                        - artists\n                      span: \"0:6-13\"\n                span: \"0:1-13\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - derive\n                    span: \"0:14-20\"\n                  args:\n                    - Literal:\n                        Integer: 5\n                      span: \"0:25-26\"\n                      alias: x\n                span: \"0:14-26\"\n          span: \"0:1-26\"\n      span: \"0:0-26\"\n    \"# )\n}\n\n#[test]\nfn test_coalesce() {\n    assert_yaml_snapshot!(parse_source(r###\"\n        from employees\n        derive amount = amount ?? 0\n        \"###).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:9-13\"\n                  args:\n                    - Ident:\n                        - employees\n                      span: \"0:14-23\"\n                span: \"0:9-23\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - derive\n                    span: \"0:32-38\"\n                  args:\n                    - Binary:\n                        left:\n                          Ident:\n                            - amount\n                          span: \"0:48-54\"\n                        op: Coalesce\n                        right:\n                          Literal:\n                            Integer: 0\n                          span: \"0:58-59\"\n                      span: \"0:48-59\"\n                      alias: amount\n                span: \"0:32-59\"\n          span: \"0:9-59\"\n      span: \"0:0-59\"\n    \"# )\n}\n\n#[test]\nfn test_literal() {\n    assert_yaml_snapshot!(parse_source(r###\"\n        derive x = true\n        \"###).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - derive\n              span: \"0:9-15\"\n            args:\n              - Literal:\n                  Boolean: true\n                span: \"0:20-24\"\n                alias: x\n          span: \"0:9-24\"\n      span: \"0:0-24\"\n    \"#)\n}\n\n#[test]\nfn test_allowed_idents() {\n    assert_yaml_snapshot!(parse_source(r###\"\n        from employees\n        join _salary (==employee_id) # table with leading underscore\n        filter first_name == $1\n        select {_employees._underscored_column}\n        \"###).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:9-13\"\n                  args:\n                    - Ident:\n                        - employees\n                      span: \"0:14-23\"\n                span: \"0:9-23\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - join\n                    span: \"0:32-36\"\n                  args:\n                    - Ident:\n                        - _salary\n                      span: \"0:37-44\"\n                    - Unary:\n                        op: EqSelf\n                        expr:\n                          Ident:\n                            - employee_id\n                          span: \"0:48-59\"\n                      span: \"0:46-59\"\n                span: \"0:32-60\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - filter\n                    span: \"0:101-107\"\n                  args:\n                    - Binary:\n                        left:\n                          Ident:\n                            - first_name\n                          span: \"0:108-118\"\n                        op: Eq\n                        right:\n                          Param: \"1\"\n                          span: \"0:122-124\"\n                      span: \"0:108-124\"\n                span: \"0:101-124\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - select\n                    span: \"0:133-139\"\n                  args:\n                    - Tuple:\n                        - Ident:\n                            - _employees\n                            - _underscored_column\n                          span: \"0:141-171\"\n                      span: \"0:140-172\"\n                span: \"0:133-172\"\n          span: \"0:9-172\"\n      span: \"0:0-172\"\n    \"#)\n}\n\n#[test]\nfn test_gt_lt_gte_lte() {\n    assert_yaml_snapshot!(parse_source(r###\"\n        from people\n        filter age >= 100\n        filter num_grandchildren <= 10\n        filter salary > 0\n        filter num_eyes < 2\n        \"###).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:9-13\"\n                  args:\n                    - Ident:\n                        - people\n                      span: \"0:14-20\"\n                span: \"0:9-20\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - filter\n                    span: \"0:29-35\"\n                  args:\n                    - Binary:\n                        left:\n                          Ident:\n                            - age\n                          span: \"0:36-39\"\n                        op: Gte\n                        right:\n                          Literal:\n                            Integer: 100\n                          span: \"0:43-46\"\n                      span: \"0:36-46\"\n                span: \"0:29-46\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - filter\n                    span: \"0:55-61\"\n                  args:\n                    - Binary:\n                        left:\n                          Ident:\n                            - num_grandchildren\n                          span: \"0:62-79\"\n                        op: Lte\n                        right:\n                          Literal:\n                            Integer: 10\n                          span: \"0:83-85\"\n                      span: \"0:62-85\"\n                span: \"0:55-85\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - filter\n                    span: \"0:94-100\"\n                  args:\n                    - Binary:\n                        left:\n                          Ident:\n                            - salary\n                          span: \"0:101-107\"\n                        op: Gt\n                        right:\n                          Literal:\n                            Integer: 0\n                          span: \"0:110-111\"\n                      span: \"0:101-111\"\n                span: \"0:94-111\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - filter\n                    span: \"0:120-126\"\n                  args:\n                    - Binary:\n                        left:\n                          Ident:\n                            - num_eyes\n                          span: \"0:127-135\"\n                        op: Lt\n                        right:\n                          Literal:\n                            Integer: 2\n                          span: \"0:138-139\"\n                      span: \"0:127-139\"\n                span: \"0:120-139\"\n          span: \"0:9-139\"\n      span: \"0:0-139\"\n    \"#)\n}\n\n#[test]\nfn test_assign() {\n    assert_yaml_snapshot!(parse_source(r###\"\nfrom employees\njoin s=salaries (==id)\n        \"###).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:1-5\"\n                  args:\n                    - Ident:\n                        - employees\n                      span: \"0:6-15\"\n                span: \"0:1-15\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - join\n                    span: \"0:16-20\"\n                  args:\n                    - Ident:\n                        - salaries\n                      span: \"0:23-31\"\n                      alias: s\n                    - Unary:\n                        op: EqSelf\n                        expr:\n                          Ident:\n                            - id\n                          span: \"0:35-37\"\n                      span: \"0:33-37\"\n                span: \"0:16-38\"\n          span: \"0:1-38\"\n      span: \"0:0-38\"\n    \"#);\n}\n\n#[test]\nfn test_unicode() {\n    let source = \"from tète\";\n    assert_yaml_snapshot!(parse_source(source).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - from\n              span: \"0:0-4\"\n            args:\n              - Ident:\n                  - tète\n                span: \"0:5-10\"\n          span: \"0:0-10\"\n      span: \"0:0-10\"\n    \"#);\n}\n\n#[test]\nfn test_var_defs() {\n    assert_yaml_snapshot!(parse_source(r#\"\n        let a = (\n            x\n        )\n        \"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: a\n        value:\n          Ident:\n            - x\n          span: \"0:17-42\"\n      span: \"0:0-42\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(r#\"\n        x\n        into a\n        \"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Into\n        name: a\n        value:\n          Ident:\n            - x\n          span: \"0:9-10\"\n      span: \"0:0-25\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(r#\"\n        x\n        \"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Ident:\n            - x\n          span: \"0:9-10\"\n      span: \"0:0-10\"\n    \"#);\n}\n\n#[test]\nfn test_array() {\n    assert_yaml_snapshot!(parse_source(r#\"\n        let a = [1, 2,]\n        let a = [false, \"hello\"]\n        \"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: a\n        value:\n          Array:\n            - Literal:\n                Integer: 1\n              span: \"0:18-19\"\n            - Literal:\n                Integer: 2\n              span: \"0:21-22\"\n          span: \"0:17-24\"\n      span: \"0:0-24\"\n    - VarDef:\n        kind: Let\n        name: a\n        value:\n          Array:\n            - Literal:\n                Boolean: false\n              span: \"0:42-47\"\n            - Literal:\n                String: hello\n              span: \"0:49-56\"\n          span: \"0:41-57\"\n      span: \"0:24-57\"\n    \"#);\n}\n\n#[test]\nfn test_annotation() {\n    assert_yaml_snapshot!(parse_source(r#\"\n        @{binding_strength=1}\n        let add = a b -> a + b\n        \"#).unwrap(), @r#\"\n    - VarDef:\n        kind: Let\n        name: add\n        value:\n          Func:\n            return_ty: ~\n            body:\n              Binary:\n                left:\n                  Ident:\n                    - a\n                  span: \"0:56-57\"\n                op: Add\n                right:\n                  Ident:\n                    - b\n                  span: \"0:60-61\"\n              span: \"0:56-61\"\n            params:\n              - name: a\n                default_value: ~\n              - name: b\n                default_value: ~\n            named_params: []\n          span: \"0:49-61\"\n      span: \"0:0-61\"\n      annotations:\n        - expr:\n            Tuple:\n              - Literal:\n                  Integer: 1\n                span: \"0:28-29\"\n                alias: binding_strength\n            span: \"0:10-30\"\n    \"#);\n    parse_source(\n        r#\"\n        @{binding_strength=1}\n        let add = a b -> a + b\n        \"#,\n    )\n    .unwrap();\n\n    parse_source(\n        r#\"\n        @{binding_strength=1}\n        # comment\n        let add = a b -> a + b\n        \"#,\n    )\n    .unwrap();\n\n    parse_source(\n        r#\"\n        @{binding_strength=1}\n\n\n        let add = a b -> a + b\n        \"#,\n    )\n    .unwrap();\n\n    parse_source(\n        r#\"\n        @{binding_strength=1}@{binding_strength=2}\n        let add = a b -> a + b\n        \"#,\n    )\n    .unwrap_err();\n}\n\n#[test]\nfn check_valid_version() {\n    let stmt = format!(\n        r#\"\n        prql version:\"{}\"\n        \"#,\n        env!(\"CARGO_PKG_VERSION_MAJOR\")\n    );\n    assert!(parse_source(&stmt).is_ok());\n\n    let stmt = format!(\n        r#\"\n            prql version:\"{}.{}\"\n            \"#,\n        env!(\"CARGO_PKG_VERSION_MAJOR\"),\n        env!(\"CARGO_PKG_VERSION_MINOR\")\n    );\n    assert!(parse_source(&stmt).is_ok());\n\n    let stmt = format!(\n        r#\"\n            prql version:\"{}.{}.{}\"\n            \"#,\n        env!(\"CARGO_PKG_VERSION_MAJOR\"),\n        env!(\"CARGO_PKG_VERSION_MINOR\"),\n        env!(\"CARGO_PKG_VERSION_PATCH\"),\n    );\n    assert!(parse_source(&stmt).is_ok());\n}\n\n#[test]\nfn check_invalid_version() {\n    let stmt = format!(\n        \"prql version:{}\\n\",\n        env!(\"CARGO_PKG_VERSION_MAJOR\").parse::<usize>().unwrap() + 1\n    );\n    assert!(parse_source(&stmt).is_err());\n}\n\n#[test]\nfn test_target() {\n    assert_yaml_snapshot!(parse_source(\n            r#\"\n          prql target:sql.sqlite\n\n          from film\n          remove film2\n        \"#,\n        )\n        .unwrap(), @r#\"\n    - QueryDef:\n        version: ~\n        other:\n          target: sql.sqlite\n      span: \"0:0-34\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:45-49\"\n                  args:\n                    - Ident:\n                        - film\n                      span: \"0:50-54\"\n                span: \"0:45-54\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - remove\n                    span: \"0:65-71\"\n                  args:\n                    - Ident:\n                        - film2\n                      span: \"0:72-77\"\n                span: \"0:65-77\"\n          span: \"0:45-77\"\n      span: \"0:34-77\"\n    \"#);\n}\n\n#[test]\nfn test_number() {\n    // We don't allow trailing periods\n    assert!(parse_source(\n        r#\"\n    from artists\n    derive x = 1.\"#\n    )\n    .is_err());\n}\n\n#[test]\nfn doc_comment() {\n    use insta::assert_yaml_snapshot;\n\n    assert_yaml_snapshot!(parse_source(r###\"\n    from artists\n    derive x = 5\n    \"###).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:5-9\"\n                  args:\n                    - Ident:\n                        - artists\n                      span: \"0:10-17\"\n                span: \"0:5-17\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - derive\n                    span: \"0:22-28\"\n                  args:\n                    - Literal:\n                        Integer: 5\n                      span: \"0:33-34\"\n                      alias: x\n                span: \"0:22-34\"\n          span: \"0:5-34\"\n      span: \"0:0-34\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(r###\"\n    from artists\n\n    #! This is a doc comment\n\n    derive x = 5\n    \"###).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - from\n              span: \"0:5-9\"\n            args:\n              - Ident:\n                  - artists\n                span: \"0:10-17\"\n          span: \"0:5-17\"\n      span: \"0:0-17\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          FuncCall:\n            name:\n              Ident:\n                - derive\n              span: \"0:53-59\"\n            args:\n              - Literal:\n                  Integer: 5\n                span: \"0:64-65\"\n                alias: x\n          span: \"0:53-65\"\n      span: \"0:47-65\"\n      doc_comment: \" This is a doc comment\"\n    \"#);\n\n    assert_yaml_snapshot!(parse_source(r###\"\n    #! This is a doc comment\n    from artists\n    derive x = 5\n    \"###).unwrap(), @r#\"\n    - VarDef:\n        kind: Main\n        name: main\n        value:\n          Pipeline:\n            exprs:\n              - FuncCall:\n                  name:\n                    Ident:\n                      - from\n                    span: \"0:34-38\"\n                  args:\n                    - Ident:\n                        - artists\n                      span: \"0:39-46\"\n                span: \"0:34-46\"\n              - FuncCall:\n                  name:\n                    Ident:\n                      - derive\n                    span: \"0:51-57\"\n                  args:\n                    - Literal:\n                        Integer: 5\n                      span: \"0:62-63\"\n                      alias: x\n                span: \"0:51-63\"\n          span: \"0:34-63\"\n      span: \"0:29-63\"\n      doc_comment: \" This is a doc comment\"\n    \"#);\n\n    assert_debug_snapshot!(parse_source(r###\"\n    from artists #! This is a doc comment\n    \"###).unwrap_err(), @r#\"\n    [\n        Error {\n            kind: Error,\n            span: Some(\n                0:18-42,\n            ),\n            reason: Simple(\n                \"unexpected #! This is a doc comment\\n\",\n            ),\n            hints: [],\n            code: None,\n        },\n    ]\n    \"#);\n}\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\n# Generally run 1 behind latest\nchannel = \"1.93.1\"\ncomponents = [\"rustfmt\", \"clippy\"]\n# We want two targets: wasm32, and the default target for the platform, which we\n# don't list here. (i.e. we use each platform to test each platform)\ntargets = [\"wasm32-unknown-unknown\"]\n"
  },
  {
    "path": "web/.gitignore",
    "content": "build\n\n# the old build dir\nbook/book\n"
  },
  {
    "path": "web/Taskfile.yaml",
    "content": "# yaml-language-server: $schema=https://json.schemastore.org/taskfile.json\n\nversion: \"3\"\n\ntasks:\n  build:\n    desc:\n      Build the web products for distribution - Website, Book, and Playground.\n    cmds:\n      - cd website && hugo --minify\n      - cd book && mdbook build\n      - mkdir -p website/public\n      # Copy the book into the website path, using rsync so it only copies new files\n      - rsync -ai --checksum --delete --no-times book/book/ website/public/book/\n      # (we don't use `build-playground-dependencies`, since that uses the dev profile)\n      - cd playground && npm ci && npm run build\n      # We place the playground app in a nested path, because we want to use\n      # prql-lang.org/playground with an iframe containing the playground.\n      # Possibly there's a more elegant way of doing this...\n      - rsync -ai --checksum --delete --no-times playground/dist/\n        website/public/playground/playground/\n\n  run-website:\n    desc: Build & serve the static website for interactive development.\n    dir: website\n    cmds:\n      - hugo server --bind 0.0.0.0\n\n  run-book:\n    desc: Build & serve the book for interactive development.\n    dir: book\n    cmds:\n      - mdbook serve --port=3000 -n 0.0.0.0\n\n  run-playground:\n    desc: Build & serve the playground for interactive development.\n    dir: playground\n    env:\n      PROFILE: dev\n    cmds:\n      - task: build-playground-dependencies\n      - npm run dev\n\n  build-playground-dependencies:\n    # Check if npm dependencies for the playground need to be updated\n    # Use task's sources/generates to see if package.json,\n    # or anything in crates or bindings was updated after the\n    # node_modules was rebuilt\n    desc: Install deps, checking whether a dependency recently changed\n    dir: playground\n    env:\n      PROFILE: dev\n    cmds:\n      - npm ci\n    # Note that now we have `PROFILE: dev`, the build is much much faster, and\n    # we could remove this sources check if it became inconvenient.\n    sources:\n      - package.json\n      - package-lock.json\n      - ../../prqlc/**/*.rs\n      - ../../prqlc/bindings/**/*.rs\n# These tasks have been factored out in favor of the remaining tasks\n# run-web:\n#   desc: Build & serve the website & playground.\n#   summary:\n#     Note that this only live-reloads the static website; and requires\n#     rerunning to pick up playground & book changes.\n#   dir: web/website\n#   cmds:\n#     - task: build\n#     # Then start web server with rendering to disk, so it picks up the playground files.\n#     - hugo server --renderToDisk\n\n# run-playground-cached:\n#   desc: Build & serve the playground, without rebuilding rust code.\n#   dir: web/playground\n#   cmds:\n#     - task: install-playground-dependencies\n#     - npm start\n"
  },
  {
    "path": "web/book/Cargo.toml",
    "content": "[package]\nname = \"mdbook-prql\"\npublish = false\n\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\nversion.workspace = true\n\n[lib]\nbench = false\ndoc = false\ndoctest = false\n\n[[bin]]\nbench = false\nname = \"mdbook-prql\"\ntest = false\n\n[target.'cfg(not(target_family=\"wasm\"))'.dependencies]\nansi-to-html = \"0.2.2\"\nanyhow = { workspace = true }\nitertools = { workspace = true }\nmdbook-core = { version = \"0.5.2\", default-features = false }\nmdbook-preprocessor = { version = \"0.5.2\", default-features = false }\nprqlc = { path = \"../../prqlc/prqlc\", default-features = false }\npulldown-cmark = { version = \"0.13.0\", default-features = false }\npulldown-cmark-to-cmark = \"22.0.0\"\nserde_json = { workspace = true }\nstrum = { workspace = true }\nstrum_macros = { workspace = true }\n\n[target.'cfg(not(target_family=\"wasm\"))'.dev-dependencies]\nanstream = { version = \"1.0.0\" }\nglobset = \"0.4.18\"\ninsta = { workspace = true }\nlog = { workspace = true }\nregex = \"1.12.3\"\nserde_json = { workspace = true }\nserde_yaml = { workspace = true }\nsimilar-asserts = { workspace = true }\nwalkdir = \"2.5.0\"\n\n[package.metadata.release]\ntag-name = \"{{version}}\"\ntag-prefix = \"\"\n"
  },
  {
    "path": "web/book/README.md",
    "content": "# PRQL language book\n\nThese docs serve as a language book, for users of the language. They should be\nfriendly & accessible, at a minimum to those who understand basic SQL.\n\n## Running\n\nInstall all required PRQL dev tools with:\n\n```sh\ntask setup-dev\n```\n\n...or for the precise cargo command, run `cargo install --locked mdbook`. For\nthe complete build, add any `mdbook` crates listed in the `Taskfile.yaml`.\n\nAnd then to build & serve locally[^1]:\n\n```sh\ntask web:run-book\n```\n\n[^1]: ...which is equivalent to:\n\n    ```sh\n    cd book\n    mdbook serve\n    ```\n"
  },
  {
    "path": "web/book/book.toml",
    "content": "[book]\ndescription = \"Modern language for transforming data — a simple, powerful, pipelined SQL replacement\"\nlanguage = \"en\"\ntitle = \"PRQL language book\"\n\n[output.html]\nadditional-css = [\"comparison-table.css\"]\nadditional-js = [\"highlight-prql.js\"]\ngit-repository-url = \"https://github.com/PRQL/prql\"\n\n[preprocessor.prql]\n# This is required because mdbook-prql isn't necessarily installed; maybe\n# there's a better way.\n#\ncommand = \"cargo run --bin mdbook-prql\"\n\n[preprocessor.footnote]\n"
  },
  {
    "path": "web/book/comparison-table.css",
    "content": "/* Styling for Tables */\n\n.comparison {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1rem;\n  width: 100%;\n  padding-bottom: 3rem;\n}\n.comparison > div {\n  flex: 1 0 0;\n  display: flex;\n  flex-direction: column;\n  padding-bottom: 0.5rem;\n}\n.comparison h4 {\n  margin: 0;\n}\n.comparison pre {\n  margin: 0;\n  flex-grow: 1;\n}\n.comparison code {\n  padding: 0.5rem;\n  min-height: calc(100% - 1rem);\n}\n"
  },
  {
    "path": "web/book/highlight-prql.js",
    "content": "/*\nLanguage: PRQL\nDescription: PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement.\nCategory: common, database\nRequires: markdown.js\nWebsite: https://prql-lang.org/\n*/\n\n// Syntax highlighting for PRQL.\n\n// Keep consistent with\n// https://github.com/PRQL/prql/blob/main/web/website/themes/prql-theme/static/highlight/prql.js\n// TODO: can we import one from the other at build time?\n\n// Inspired by [Pest's book](https://github.com/pest-parser/book)\n\n// mdBook exposes a minified version of highlight.js, so the language\n// definition objects below have abbreviated property names:\n//     \"b\"  => begin\n//     \"e\"  => end\n//     \"c\"  => contains\n//     \"k\"  => keywords\n//     \"cN\" => className\n\n// TODO:\n// - Can we represent strings with the actual rule of >= 3 quotes?\n// - Aliases seem a bit strong?\n// - Can we represent the inner s & f string items?\n\nformatting = function (hljs) {\n  const BUILTIN_FUNCTIONS = [\n    // Aggregate functions\n    \"any\",\n    \"average\",\n    \"concat_array\",\n    \"count\",\n    \"every\",\n    \"max\",\n    \"min\",\n    \"stddev\",\n    \"sum\",\n    // File reading functions\n    \"read_csv\",\n    \"read_json\",\n    \"read_parquet\",\n    // List functions\n    \"all\",\n    \"map\",\n    \"zip\",\n    \"_eq\",\n    \"_is_null\",\n    // Misc functions\n    \"from_text\",\n    // Window functions\n    \"lag\",\n    \"lead\",\n    \"first\",\n    \"last\",\n    \"rank\",\n    \"rank_dense\",\n    \"row_number\",\n  ];\n\n  const MODULES = [\"date\", \"math\", \"text\"];\n\n  const DATATYPES = [\n    \"bool\",\n    \"float\",\n    \"int\",\n    \"int8\",\n    \"int16\",\n    \"int32\",\n    \"int64\",\n    \"text\",\n    \"timestamp\",\n  ];\n\n  const TRANSFORMS = [\n    \"aggregate\",\n    \"append\",\n    \"derive\",\n    \"filter\",\n    \"from\",\n    \"group\",\n    \"join\",\n    \"select\",\n    \"sort\",\n    \"take\",\n    \"union\",\n    \"window\",\n  ];\n\n  const KEYWORDS = [\"let\", \"prql\", \"into\", \"case\", \"in\", \"as\", \"module\"];\n\n  const CHAR_ESCAPE = {\n    scope: \"char.escape\",\n    match: /\\\\\\\\|\\\\([bfnrt]|u{[0-9A-Fa-f]{1,6}}|x[0-9A-Fa-f]{2})/,\n  };\n\n  return {\n    name: \"PRQL\",\n    case_insensitive: true,\n    keywords: {\n      built_in: BUILTIN_FUNCTIONS,\n      module: MODULES,\n      keyword: [...TRANSFORMS, ...BUILTIN_FUNCTIONS, ...KEYWORDS],\n      literal: \"false true null\",\n      type: DATATYPES,\n    },\n    contains: [\n      {\n        // docblock\n        begin: \"#!\",\n        end: \"$\",\n        subLanguage: \"markdown\",\n        relevance: 0,\n      },\n      hljs.COMMENT(\"#\", \"$\"),\n      {\n        // named arg\n        scope: \"params\",\n        begin: /\\w+\\s*:/,\n        end: \"\",\n        relevance: 10,\n      },\n      {\n        // meta prql for target and version\n        scope: \"meta\",\n        match: /^prql/,\n      },\n      // This seems much too strong at the moment, so disabling. I think ideally\n      // we'd have it for aliases but not assigns.\n      // {\n      //   // assign\n      //   scope: { 1: \"variable\" },\n      //   match: [/\\w+\\s*/, /=[^=]/],\n      //   relevance: 10,\n      // },\n      {\n        // date\n        scope: \"string\",\n        match: /@(\\d*|-|\\.\\d|:|T)+Z?/,\n        relevance: 10,\n      },\n      {\n        // interval\n        scope: \"string\",\n        // Add more as needed\n        match:\n          /\\d+(years|months|weeks|days|hours|minutes|seconds|milliseconds|microseconds)/,\n        relevance: 10,\n      },\n      {\n        scope: \"string\",\n        relevance: 10,\n        variants: [\n          {\n            begin: 'r\"\"\"',\n            end: '\"\"\"',\n          },\n          {\n            begin: 'r\"',\n            end: '\"',\n          },\n        ],\n      },\n      {\n        // interpolation strings: s-strings are variables and f-strings are\n        // strings? (Though possibly that's too cute, open to adjusting)\n        //\n        scope: \"variable\",\n        relevance: 10,\n        variants: [\n          {\n            begin: 's\"\"\"',\n            end: '\"\"\"',\n          },\n          {\n            begin: 's\"',\n            end: '\"',\n          },\n        ],\n        contains: [\n          // I tried having the `f` / `s` be marked differently, but I don't\n          // think it's possible to have a subscope within the begin / end.\n          {\n            // I think `variable` is the right scope rather than defaulting to\n            // white, but not 100% sure; using `subst` is suggested in the docs.\n            scope: \"variable\",\n            begin: /\\{/,\n            end: /\\}/,\n          },\n        ],\n      },\n      {\n        scope: \"string\",\n        relevance: 10,\n        variants: [\n          {\n            begin: 'f\"\"\"',\n            end: '\"\"\"',\n          },\n          {\n            begin: 'f\"',\n            end: '\"',\n          },\n        ],\n        contains: [\n          CHAR_ESCAPE,\n          {\n            scope: \"variable\",\n            begin: \"f\",\n            end: '\"',\n            // excludesEnd: true,\n          },\n          // TODO: would be nice to have this be a different color, but I don't\n          // think it's possible to have a subscope within the begin / end.\n          // {\n          //   scope: \"punctuation\",\n          //   match: /{|}/,\n          // },\n          {\n            scope: \"variable\",\n            begin: /\\{/,\n            end: /\\}/,\n          },\n        ],\n      },\n      {\n        // normal string\n        scope: \"string\",\n        relevance: 10,\n        variants: [\n          // TODO: is there a way of encoding the actual rule here? Otherwise\n          // we're just adding the variants we use...\n          {\n            begin: '\"\"\"\"\"',\n            end: '\"\"\"\"\"',\n          },\n          {\n            begin: '\"\"\"',\n            end: '\"\"\"',\n          },\n          {\n            begin: '\"',\n            end: '\"',\n          },\n          {\n            begin: \"'\",\n            end: \"'\",\n          },\n        ],\n        contains: [CHAR_ESCAPE],\n      },\n      { scope: \"punctuation\", match: /[\\[\\]{}(),]/ },\n      {\n        scope: \"operator\",\n        match: /==|~=|\\+|\\-|\\/|\\*|!=|->|=>|<=|>=|&&|\\|\\||<|>/,\n        relevance: 10,\n      },\n      {\n        scope: \"number\",\n        // Regex explanation:\n        // 1. `\\b`: asserts a word boundary. This ensures that the pattern matches numbers that are distinct words or at the boundaries of words.\n        // 2. `(\\d[_\\d]*(e|E)\\d[_\\d]*)`: This is the first alternative in the main group and matches numbers in scientific notation:\n        //     - `\\d`: matches a digit (0-9).\n        //     - `[_\\d]*`: matches zero or more underscores or digits, representing the numbers before the `e` in scientific notation.\n        //     - `(e|E)`: matches the letter 'e' or 'E' for scientific notation.\n        //     - `\\d`: matches a digit (0-9), the beginning of the exponent.\n        //     - `[_\\d]*`: matches zero or more underscores or digits, representing the numbers after the `e` in scientific notation.\n        // 3. `(\\d[_\\d]*|(\\d\\.[\\d_]*\\d))`: This is the second alternative in the main group and matches standard numbers without the scientific notation:\n        //     - `\\d[_\\d]*`: matches a sequence starting with a digit and followed by zero or more digits or underscores.\n        //     - `|`: OR\n        //     - `(\\d\\.[\\d_]*\\d)`: matches numbers with a decimal point:\n        //         - `\\d`: matches the digit(s) before the decimal point.\n        //         - `\\.`: matches the decimal point.\n        //         - `[\\d_]*\\d`: matches digits after the decimal point, ensuring the sequence ends in a digit and not a trailing underscore.\n        // 4. `(\\.[\\d_]+)`: This is the third alternative in the main group:\n        //     - `\\.`: matches a literal dot, so this alternative captures numbers that begin with a decimal point.\n        //     - `[\\d_]+`: matches one or more digits or underscores, for the sequence after the initial dot.\n        match:\n          /\\b((\\d[_\\d]*(e|E)\\d[_\\d]*)|(\\d[_\\d]*|(\\d\\.[\\d_]*\\d))|(\\.[\\d_]+))/,\n        relevance: 10,\n      },\n      {\n        // range\n        scope: \"symbol\",\n        match: /\\.{2}/,\n        relevance: 10,\n      },\n      // Unfortunately this just overrides any keywords. It's also not\n      // complete — it only handles functions at the beginning of a line.\n      // I spent several hours trying to get hljs to handle this, but\n      // because there's no recursion, I'm not sure it's possible.\n      // Possibly we could hook into `on:begin` and implement it\n      // ourselves, but this would be a lot of overhead.\n      // { // function\n      //     keywords: TRANSFORMS.join(' '),\n      //     beginScope: { 1: 'title.function' },\n      //     begin: [/^\\s*[a-zA-Z]+/, /(\\s+[a-zA-Z]+)+/],\n      //     relevance: 10\n      // },\n    ],\n  };\n};\n\nhljs.registerLanguage(\"prql\", formatting);\n\n// These lines should only exists in the book, not the website.\n\n// This file is unfortunately inserted after the default highlight.js\n// invocation, which tags unknown-language blocks with CSS classes but doesn't\n// highlight them.\nArray.from(document.querySelectorAll(\"code.language-prql\")).forEach((a) =>\n  hljs.highlightElement(a),\n);\n"
  },
  {
    "path": "web/book/src/README.md",
    "content": "# PRQL Language Book\n\n**P**ipelined **R**elational **Q**uery **L**anguage, pronounced \"Prequel\".\n\nPRQL is a modern language for transforming data — a simple, powerful, pipelined\nSQL replacement. Like SQL, it's readable, explicit and declarative. Unlike SQL,\nit forms a logical pipeline of transformations, and supports abstractions such\nas variables and functions. It can be used with any database that uses SQL,\nsince it compiles to SQL.\n\nThis book serves as a tutorial and reference guide on the language and the\nbroader project. It currently has three sections, navigated by links on the\nleft:\n\n- **Tutorial** — A friendly & accessible guide for learning PRQL. It has a\n  gradual increase of difficulty and requires only basic understanding of\n  programming languages. Knowledge of SQL is beneficial, because of many\n  comparisons to SQL, but not required.\n- **Reference** — In-depth information about the PRQL language. Includes\n  justifications for language design decisions and formal specifications for\n  parts of the language.\n- **Project** — General information about the project, tooling and development.\n\n---\n\n**Examples of PRQL** with a comparison to the generated SQL. PRQL queries can be\nas simple as:\n\n```prql\nfrom tracks\nfilter artist == \"Bob Marley\"  # Each line transforms the previous result\naggregate {                    # `aggregate` reduces each column to a value\n  plays    = sum plays,\n  longest  = max length,\n  shortest = min length,       # Trailing commas are allowed\n}\n```\n\n...and here's a larger example:\n\n```prql\nfrom employees\nfilter start_date > @2021-01-01            # Clear date syntax\nderive {                                   # `derive` adds columns / variables\n  gross_salary = salary + (tax ?? 0),      # Terse coalesce\n  gross_cost = gross_salary + benefits,    # Variables can use other variables\n}\nfilter gross_cost > 0\ngroup {title, country} (                   # `group` runs a pipeline over each group\n  aggregate {                              # `aggregate` reduces each group to a value\n    average gross_salary,\n    sum_gross_cost = sum gross_cost,       # `=` sets a column name\n  }\n)\nfilter sum_gross_cost > 100_000            # `filter` replaces both of SQL's `WHERE` & `HAVING`\nderive id = f\"{title}_{country}\"           # F-strings like Python\nderive country_code = s\"LEFT(country, 2)\"  # S-strings permit SQL as an escape hatch\nsort {sum_gross_cost, -country}            # `-country` means descending order\ntake 1..20                                 # Range expressions (also valid as `take 20`)\n```\n"
  },
  {
    "path": "web/book/src/SUMMARY.md",
    "content": "<!-- markdownlint-disable MD042 — some pages aren't finished yet (though the graying out of top level pages is not ideal — it's either that, or links to pages that are blank. Or maybe we try and write a useful page for each heading?) -->\n\n# Introduction\n\n[Introduction](./README.md)\n\n# Tutorial\n\nA friendly & accessible guide for learning PRQL. It has a gradual increase of\ndifficulty and requires only basic understanding of programming languages.\nKnowledge of SQL is beneficial, because of many comparisons to SQL, but not\nrequired.\n\n- [Relations](./tutorial/relations.md)\n- [Filtering](./tutorial/filtering.md)\n- [Aggregation](./tutorial/aggregation.md)\n\n<!-- We used to have a \"How do I\", which I think would be good, but we didn't build enough to maintain it. If we find the Reference or Tutorial has enough content that we could move here, we could start it again  -->\n<!-- # How do I? -->\n\n# Reference\n\nIn-depth information about the PRQL language. Includes justifications for\nlanguage design decisions and formal specifications for parts of the language.\n\n- [Syntax](./reference/syntax/README.md)\n  - [Literals](./reference/syntax/literals.md)\n  - [Strings](./reference/syntax/strings.md)\n    - [F-strings](./reference/syntax/f-strings.md)\n    - [R-strings](./reference/syntax/r-strings.md)\n    - [S-strings](./reference/syntax/s-strings.md)\n  - [Tuples](./reference/syntax/tuples.md)\n  - [Arrays](./reference/syntax/arrays.md)\n  - [Identifiers & keywords](./reference/syntax/keywords.md)\n  - [Function calls](./reference/syntax/function-calls.md)\n  - [Pipes](./reference/syntax/pipes.md)\n  - [Operators](./reference/syntax/operators.md)\n  - [Case](./reference/syntax/case.md)\n  - [Ranges](./reference/syntax/ranges.md)\n  - [Comments](./reference/syntax/comments.md)\n  - [Parameters](./reference/syntax/parameters.md)\n\n- [Importing data](./reference/data/README.md)\n  - [From](./reference/data/from.md)\n  - [Reading files](./reference/data/read-files.md)\n  - [Ad-hoc data](./reference/data/relation-literals.md)\n\n- [Declarations]()\n  <!-- I don't know what to call this section. -->\n  - [Variables — `let` & `into`](./reference/declarations/variables.md)\n  - [Functions](./reference/declarations/functions.md)\n\n- [Standard library](./reference/stdlib/README.md)\n  - [Transforms](./reference/stdlib/transforms/README.md)\n    - [Aggregate](./reference/stdlib/transforms/aggregate.md)\n    - [Append](./reference/stdlib/transforms/append.md)\n    - [Derive](./reference/stdlib/transforms/derive.md)\n    - [Filter](./reference/stdlib/transforms/filter.md)\n    - [Group](./reference/stdlib/transforms/group.md)\n    - [Join](./reference/stdlib/transforms/join.md)\n    - [Loop](./reference/stdlib/transforms/loop.md)\n    - [Select](./reference/stdlib/transforms/select.md)\n    - [Sort](./reference/stdlib/transforms/sort.md)\n    - [Take](./reference/stdlib/transforms/take.md)\n    - [Window](./reference/stdlib/transforms/window.md)\n\n  - [Aggregation functions]()\n  - [Date functions](./reference/stdlib/date.md)\n  - [Mathematical functions](./reference/stdlib/math.md)\n  - [Text functions](./reference/stdlib/text.md)\n  - [Removing duplicates](./reference/stdlib/distinct.md)\n\n- [Specification](./reference/spec/README.md)\n  - [Null handling](./reference/spec/null.md)\n  - [Name resolution](./reference/spec/name-resolution.md)\n  - [Modules](./reference/spec/modules.md)\n  - [Type system](./reference/spec/type-system.md)\n\n# Project\n\nGeneral information about the project, tooling and development.\n\n- [Changelog](./project/changelog.md)\n\n- [Target & version](./project/target.md)\n\n- [Bindings](./project/bindings/README.md)\n  - [.NET](./project/bindings/dotnet.md)\n  - [Elixir](./project/bindings/elixir.md)\n  - [Java](./project/bindings/java.md)\n  - [JavaScript](./project/bindings/javascript.md)\n  - [PHP](./project/bindings/php.md)\n  - [Python](./project/bindings/python.md)\n  - [R](./project/bindings/r.md)\n  - [Rust](./project/bindings/rust.md)\n\n- [Integrations](./project/integrations/README.md)\n  - [`prqlc CLI`](./project/integrations/prqlc-cli.md)\n  - [ClickHouse](./project/integrations/clickhouse.md)\n  - [Jupyter](./project/integrations/jupyter.md)\n  - [DuckDB](./project/integrations/duckdb.md)\n  - [QStudio](./project/integrations/qstudio.md)\n  - [Prefect](./project/integrations/prefect.md)\n  - [VS Code](./project/integrations/vscode.md)\n  - [PostgreSQL](./project/integrations/postgresql.md)\n  - [Databend](./project/integrations/databend.md)\n  - [Rill](./project/integrations/rill.md)\n  - [Syntax highlighting](./project/integrations/syntax-highlighting.md)\n\n- [Contributing to PRQL](./project/contributing/README.md)\n  - [Development](./project/contributing/development.md)\n  - [Language design](./project/contributing/language-design.md)\n"
  },
  {
    "path": "web/book/src/lib.rs",
    "content": "#![cfg(not(target_family = \"wasm\"))]\n\nuse std::str::FromStr;\n\nuse anyhow::{bail, Result};\nuse itertools::Itertools;\nuse mdbook_core::book::{Book, BookItem};\nuse mdbook_preprocessor::{Preprocessor, PreprocessorContext};\nuse prqlc::compile;\nuse pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};\nuse pulldown_cmark_to_cmark::cmark_with_options;\nuse strum::EnumString;\n\npub struct ComparisonPreprocessor;\n\nimpl Preprocessor for ComparisonPreprocessor {\n    fn name(&self) -> &str {\n        \"comparison-preprocessor\"\n    }\n\n    fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {\n        eprintln!(\"Running PRQL comparison preprocessor\");\n        book.for_each_mut(|item: &mut BookItem| {\n            if let BookItem::Chapter(chapter) = item {\n                let new = replace_examples(&chapter.content).unwrap();\n                chapter.content.clear();\n                chapter.content.push_str(&new);\n            }\n        });\n\n        Ok(book)\n    }\n\n    fn supports_renderer(&self, renderer: &str) -> Result<bool> {\n        Ok(renderer == \"html\")\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, EnumString, strum::Display)]\n#[strum(serialize_all = \"kebab_case\")]\npub enum LangTag {\n    Prql,\n    // The query either can't be formatted or, after being formatted, it can't\n    // be compiled.\n    NoFmt,\n    // Ignore it, as though it's not PRQL.\n    NoEval,\n    // The query can't be compiled.\n    Error,\n    // Don't test the query.\n    NoTest,\n    #[strum(default)]\n    Other(String),\n}\n\n/// Returns the language of a code block, divided by spaces\n/// For example: ```prql no-test\npub fn code_block_lang_tags(event: &Event) -> Option<Vec<LangTag>> {\n    if let Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(lang))) = event {\n        Some(lang.split(' ').map(LangTag::from_str).try_collect().ok()?)\n    } else {\n        None\n    }\n}\n\nfn replace_examples(text: &str) -> Result<String> {\n    let mut parser = Parser::new_ext(text, Options::all());\n    let mut cmark_acc = vec![];\n\n    while let Some(event) = parser.next() {\n        // If it's there no tag, or it's not PRQL, or it has `no-eval`, just\n        // push it and continue.\n        let Some(lang_tags) = code_block_lang_tags(&event) else {\n            cmark_acc.push(event.clone());\n            continue;\n        };\n        if !lang_tags.contains(&LangTag::Prql) {\n            cmark_acc.push(event.clone());\n            continue;\n        }\n\n        for tag in &lang_tags {\n            if matches!(tag, LangTag::Other(_)) {\n                bail!(\"Unknown code block language: {}\", tag)\n            }\n        }\n\n        if lang_tags.contains(&LangTag::NoEval) {\n            cmark_acc.push(event.clone());\n            continue;\n        }\n\n        let Some(Event::Text(text)) = parser.next() else {\n            bail!(\"Expected text within code block\")\n        };\n\n        let prql = text.to_string();\n        let result = compile(&prql, &prqlc::Options::default().no_signature());\n\n        if lang_tags.contains(&LangTag::NoTest) {\n            cmark_acc.push(Event::Html(table_of_prql_only(&prql).into()));\n        } else if lang_tags.contains(&LangTag::Error) {\n            // This logic is implemented again, and better, in\n            // [../tests/snapshot.rs], so would be fine to just skip here.\n            let error_message = match result {\n                Ok(sql) => {\n                    anyhow::bail!(\n                        \"Query was labeled to raise an error, but succeeded.\\n{prql}\\n\\n{sql}\\n\\n\"\n                    )\n                }\n                Err(e) => ansi_to_html::convert(e.to_string().as_str()).unwrap(),\n            };\n\n            cmark_acc.push(Event::Html(table_of_error(&prql, &error_message).into()))\n        } else {\n            // Show the comparison\n            cmark_acc.push(Event::Html(\n                table_of_comparison(\n                    &prql,\n                    result\n                        .map_err(|e| {\n                            anyhow::anyhow!(\"Query raised an error:\\n\\n {prql}\\n\\n{e}\\n\\n\")\n                        })?\n                        .as_str(),\n                )\n                .into(),\n            ))\n        }\n        // Skip ending tag\n        parser.next();\n    }\n    let mut buf = String::new();\n    let opts = pulldown_cmark_to_cmark::Options::default();\n    cmark_with_options(cmark_acc.into_iter(), &mut buf, opts)?;\n\n    Ok(buf)\n}\n\nfn table_of_comparison(prql: &str, sql: &str) -> String {\n    format!(\n        r#\"\n\n<div class=\"comparison\">\n\n<div>\n<h4>PRQL</h4>\n\n```prql\n{prql}\n```\n\n</div>\n\n<div>\n<h4>SQL</h4>\n\n```sql\n{sql}\n```\n\n</div>\n\n</div>\n\"#,\n        prql = prql.trim(),\n        sql = sql,\n    )\n    .to_string()\n}\n\n// Similar to `table_of_comparison`, but without a second column.\nfn table_of_prql_only(prql: &str) -> String {\n    format!(\n        r#\"\n\n<div class=\"comparison\">\n\n<div>\n<h4>PRQL</h4>\n\n```prql\n{prql}\n```\n\n</div>\n</div>\n\"#,\n        prql = prql.trim(),\n    )\n    .to_string()\n}\n\n// Exactly the same as `table_of_comparison`, but with a different title for the second column.\nfn table_of_error(prql: &str, message: &str) -> String {\n    format!(\n        r#\"\n\n<div class=\"comparison\">\n\n<div>\n<h4>PRQL</h4>\n\n```prql\n{prql}\n```\n\n</div>\n\n<div>\n<h4>Error</h4>\n\n<pre><code class=\"hljs language-undefined\">{message}</code></pre>\n\n</div>\n\n</div>\n\"#,\n        prql = prql.trim(),\n        message = message,\n    )\n    .to_string()\n}\n\n#[test]\nfn test_replace_examples() -> Result<()> {\n    use insta::assert_snapshot;\n    // Here we do want colors\n    anstream::ColorChoice::Always.write_global();\n\n    let md = r###\"\n# PRQL Doc\n\n```prql\nfrom x\n```\n\n```python\nimport sys\n```\n\n```prql error\nthis is an error\n```\n    \"###;\n\n    assert_snapshot!(replace_examples(md)?, md, @r#\"\n    # PRQL Doc\n\n    <div class=\"comparison\">\n\n    <div>\n    <h4>PRQL</h4>\n\n    ```prql\n    from x\n    ```\n\n    </div>\n\n    <div>\n    <h4>SQL</h4>\n\n    ```sql\n    SELECT\n      *\n    FROM\n      x\n\n    ```\n\n    </div>\n\n    </div>\n\n\n    ````python\n    import sys\n    ````\n\n    <div class=\"comparison\">\n\n    <div>\n    <h4>PRQL</h4>\n\n    ```prql\n    this is an error\n    ```\n\n    </div>\n\n    <div>\n    <h4>Error</h4>\n\n    <pre><code class=\"hljs language-undefined\"><span style='color:var(--red,#a00)'>Error:</span>\n       <span style='color:#949494'>╭─[</span> :1:1 <span style='color:#949494'>]</span>\n       <span style='color:#949494'>│</span>\n     <span style='color:#949494'>1 │</span> this<span style='color:#b2b2b2'> is an error</span>\n     <span style='color:#585858'>  │</span> ──┬─\n     <span style='color:#585858'>  │</span>   ╰─── Unknown name `this`\n    <span style='color:#949494'>───╯</span>\n    </code></pre>\n\n    </div>\n\n    </div>\n    \"#);\n\n    Ok(())\n}\n\n#[test]\nfn test_admonition() -> Result<()> {\n    use insta::assert_snapshot;\n\n    let md = r#\"\n> [!NOTE]\n> This is a note.\n\n> [!WARNING]\n> This is a warning.\n\"#;\n\n    assert_snapshot!(replace_examples(md)?, @r#\"\n    > [!NOTE]\n    > This is a note.\n\n    > [!WARNING]\n    > This is a warning.\n    \"#);\n\n    Ok(())\n}\n\n#[test]\nfn test_table() -> Result<()> {\n    use insta::assert_snapshot;\n    let table = r\"\n# Syntax\n\n| a |\n|---|\n| c |\n\n\n| a   |\n|-----|\n| \\|  |\n\n\";\n\n    assert_snapshot!(replace_examples(table)?, @r\"\n    # Syntax\n\n    |a|\n    |-|\n    |c|\n\n    |a|\n    |-|\n    |\\||\n    \");\n\n    Ok(())\n}\n"
  },
  {
    "path": "web/book/src/main.rs",
    "content": "// We don't need to run this with wasm, and the features that `mdbook` uses of\n// `clap`'s don't support wasm.\n#[cfg(not(target_family = \"wasm\"))]\nfn main() {\n    use mdbook_preprocessor::{parse_input, Preprocessor};\n    use mdbook_prql::ComparisonPreprocessor;\n    use std::io;\n    use std::process;\n\n    let preprocessor = ComparisonPreprocessor;\n\n    // Handle the supports subcommand\n    if let Some(arg) = std::env::args().nth(1) {\n        if arg == \"supports\" {\n            let renderer = std::env::args().nth(2).unwrap_or_else(|| {\n                eprintln!(\"mdbook-prql: missing renderer argument for 'supports' subcommand\");\n                process::exit(1);\n            });\n\n            let supports = preprocessor\n                .supports_renderer(&renderer)\n                .expect(\"supports_renderer should not fail\");\n            process::exit(if supports { 0 } else { 1 });\n        }\n    }\n\n    // Parse input from stdin\n    let (ctx, book) = parse_input(io::stdin()).unwrap_or_else(|e| {\n        eprintln!(\"mdbook-prql: failed to parse JSON input from mdbook: {}\", e);\n        process::exit(1);\n    });\n\n    // Run the preprocessor\n    let processed_book = preprocessor.run(&ctx, book).unwrap_or_else(|e| {\n        eprintln!(\"mdbook-prql: failed to process PRQL code blocks: {}\", e);\n        process::exit(1);\n    });\n\n    // Serialize and output result\n    let output = serde_json::to_string(&processed_book).unwrap_or_else(|e| {\n        eprintln!(\"mdbook-prql: failed to serialize book to JSON: {}\", e);\n        process::exit(1);\n    });\n    println!(\"{}\", output);\n}\n\n#[cfg(target_family = \"wasm\")]\nfn main() -> ! {\n    panic!(\"Not used as a binary in wasm (but it seems cargo insists we have a `main` function).\")\n}\n"
  },
  {
    "path": "web/book/src/project/bindings/README.md",
    "content": "# Bindings\n\nPRQL has bindings for many languages. These include:\n\nWe have three tiers of bindings:\n\n- Supported\n- Unsupported\n- Nascent\n\n## Supported\n\nSupported bindings require:\n\n- A maintainer.\n- Implementations of the\n  [core compile functions](https://docs.rs/prqlc/latest/prqlc/#functions).\n- Test coverage for these functions.\n- A published package to the language's standard package repository.\n- A script in `Taskfile.yaml` to bootstrap a development environment.\n- Any dev tools, such as a linter & formatter, in pre-commit or MegaLinter.\n\nThe currently supported bindings are:\n\n- [JavaScript](./javascript.md)\n- [Python](./python.md)\n- [R](./r.md)\n- [Rust](./rust.md)\n\nMost of these are in the main PRQL repo, and we gate any changes to the\ncompiler's API on compatible changes to the bindings.\n\n## Unsupported\n\nUnsupported bindings work, but don't fulfil all of the above criteria. We don't\ngate changes to the compiler's API. If they stop working, we'll demote them to\nnascent.\n\n- [Java](./java.md)\n- [Elixir](./elixir.md)\n- `prqlc-c`, the C bindings\n\n## Nascent\n\nNascent bindings are in development, and may not yet fully work.\n\n- [.NET](./dotnet.md)\n- [PHP](./php.md)\n\n## Naming\n\nOver time, we're trying to move to a consistent naming scheme:\n\n- Crates are named `prqlc-$lang`.\n- Where possible, packages are published to each language's package repository\n  as `prqlc`.\n"
  },
  {
    "path": "web/book/src/project/bindings/dotnet.md",
    "content": "{{#include ../../../../../prqlc/bindings/dotnet/README.md}}\n"
  },
  {
    "path": "web/book/src/project/bindings/elixir.md",
    "content": "{{#include ../../../../../prqlc/bindings/elixir/README.md}}\n"
  },
  {
    "path": "web/book/src/project/bindings/java.md",
    "content": "{{#include ../../../../../prqlc/bindings/java/README.md}}\n"
  },
  {
    "path": "web/book/src/project/bindings/javascript.md",
    "content": "{{#include ../../../../../prqlc/bindings/js/README.md}}\n"
  },
  {
    "path": "web/book/src/project/bindings/php.md",
    "content": "{{#include ../../../../../prqlc/bindings/php/README.md}}\n"
  },
  {
    "path": "web/book/src/project/bindings/python.md",
    "content": "{{#include ../../../../../prqlc/bindings/prqlc-python/README.md}}\n"
  },
  {
    "path": "web/book/src/project/bindings/r.md",
    "content": "# R (prqlr)\n\nR bindings for `prqlc`.\n\n`prqlr` also includes `knitr` (R Markdown and Quarto) integration, which allows\nus to easily create documents with the PRQL conversion results embedded in.\n\nCheck out <https://prql.github.io/prqlc-r/> for more context.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> `prqlr` is generously maintained by\n> [@eitsupi](https://github.com/eitsupi) in the\n> [PRQL/prqlc-r](https://github.com/PRQL/prqlc-r) repo.\n\n## Installation\n\n```r\ninstall.packages(\"prqlr\")\n```\n"
  },
  {
    "path": "web/book/src/project/bindings/rust.md",
    "content": "Please check the documentation of the\n[prqlc crate](https://docs.rs/prqlc/latest/prqlc/).\n"
  },
  {
    "path": "web/book/src/project/changelog.md",
    "content": "{{#include ../../../../CHANGELOG.md}}\n"
  },
  {
    "path": "web/book/src/project/contributing/README.md",
    "content": "# Contributing\n\nIf you're interested in joining the community to build a better SQL, here are\nways to start:\n\n- Star the [repo](https://github.com/PRQL/prql).\n- Send a link to PRQL to a couple of people whose opinion you respect.\n- Subscribe to\n  [new releases](https://www.jessesquires.com/blog/2020/07/30/github-tip-watching-releases/)\n  for updates.\n- Follow us on [Twitter](https://twitter.com/prql_lang).\n- Join our [Discord](https://discord.gg/eQcfaCmsNc).\n- Find an issue labeled\n  [Good First Issue](https://github.com/prql/prql/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)\n  and start contributing to the code.\n- Join our\n  [fortnightly Developer Call](https://github.com/PRQL/prql/issues/1083);\n  ([iCal file](./fortnightly-dev-call.ics)).\n\nPRQL is evolving from a project with lots of excitement into a project that\nfolks are using in their work and integrating into their tools. We're actively\nlooking for collaborators to lead that growth with us.\n\n## Areas for larger contributions\n\n### Compiler\n\nThe compiler is written in Rust, and there's enough to do such that any level of\nexperience with Rust is sufficient.\n\nWe try to keep a few onboarding issues on hand under the\n[\"good first issue\" label](https://github.com/PRQL/prql/labels/good%20first%20issue).\nThese have been screened to have sufficient context to get started (and we very\nmuch welcome questions where there's some context missing).\n\nTo get started, check out the docs on [Development](./development.md) and the\n[Compiler architecture](https://github.com/PRQL/prql/blob/main/prqlc/prqlc/ARCHITECTURE.md)\n\nAnd if you have questions, there are lots of friendly people on the Discord who\nwill patiently help you.\n\n### Bindings & integrations\n\nFor PRQL to be successful, it needs to be available for the languages & tools\nthat people already use.\n\n- We currently have bindings to the PRQL compiler in a few different languages;\n  many of these can be improved, documented, and packaged in a better way.\n- If you have experience with packaging in an ecosystem that doesn't currently\n  have bindings, then creating PRQL bindings for that language we don't\n  currently support would be valuable to the project.\n- If there's a tool that you use yourself to run data queries which you think\n  would benefit from a PRQL integration, suggest one to us or the tool. If it's\n  open-source, build & share a prototype.\n\nRelevant issues are labeled\n[Integrations](https://github.com/PRQL/prql/labels/integrations).\n\n### Language design\n\nWe decide on new language features in GitHub issues, usually under\n[\"language design\" label](https://github.com/PRQL/prql/issues?q=is%3Aopen+label%3Alanguage-design+sort%3Aupdated-desc).\n\nYou can also contribute by:\n\n- Finding instances where the compiler produces incorrect results, and post a\n  bug report — feel free to use the\n  [playground](https://prql-lang.org/playground).\n- Opening an issue / append to an existing issue with examples of queries that\n  are difficult to express in PRQL — especially if more difficult than SQL.\n\nWith sufficient examples, suggest a change to the language! (Though suggestions\n_without_ examples are difficult to engage with, so please do anchor suggestions\nin examples.)\n\n### Marketing\n\n- Improve our website. We have\n  [a few issues open](https://github.com/PRQL/prql/labels/web) on this front and\n  are looking for anyone with at least some design skills.\n- Contribute towards the docs. Anything from shaping a whole section of the\n  docs, to simply improving a confusing paragraph or fixing a typo.\n- Tell people about PRQL.\n- Find a group of users who would be interested in PRQL, help them get up to\n  speed, help the project understand what they need.\n\n## Core team\n\nIf you have any questions or feedback and don't receive a response on one of the\ngeneral channels such as GitHub or Discord, feel free to reach out to:\n\n- [**@aljazerzen**](https://github.com/aljazerzen) — Aljaž Mur Eržen\n- [**@max-sixty**](https://github.com/max-sixty) — Maximilian Roos\n- [**@eitsupi**](https://github.com/eitsupi) — SHIMA Tatsuya\n- [**@snth**](https://github.com/snth) — Tobias Brandt\n\n### Core team Emeritus\n\nThank you to those who have previously served on the core team:\n\n- **@charlie-sanders** — Charlie Sanders\n"
  },
  {
    "path": "web/book/src/project/contributing/development.md",
    "content": "# Development\n\n## Setting up an initial dev environment\n\nWe can set up a local development environment sufficient for navigating,\nediting, and testing PRQL's compiler code in two minutes:\n\n- Install\n  [`rustup` & `cargo`](https://doc.rust-lang.org/cargo/getting-started/installation.html).\n- [Optional but highly recommended] Install `cargo-insta`, our testing\n  framework:\n\n  ```sh\n  cargo install cargo-insta\n  ```\n\n- That's it! Running the unit tests for the `prqlc` crate after cloning the repo\n  should complete successfully:\n\n  ```sh\n  cargo test --package prqlc --lib\n  ```\n\n  ...or, to run tests and update the test snapshots:\n\n  ```sh\n  cargo insta test --accept --package prqlc --lib\n  ```\n\n  There's more context on our tests in [How we test](#how-we-test) below.\n\nThat's sufficient for making an initial contribution to the compiler.\n\n---\n\n## Setting up a full dev environment\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> We really care about this process being easy, both because the project\n> benefits from more contributors like you, and to reciprocate your future\n> contribution. If something isn't easy, please let us know in a GitHub Issue.\n> We'll enthusiastically help you, and use your feedback to improve the scripts\n> & instructions.\n\nFor more advanced development; for example compiling for wasm or previewing the\nwebsite, we have two options:\n\n### Option 1: Use the project's `task`\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> This is tested on macOS, should work on amd64 Linux, but won't work on\n> others (include Windows), since it relies on `brew`.\n\n- [Install Task](https://taskfile.dev/installation/).\n- Then run the `setup-dev` task. This runs commands from our\n  [Taskfile.yaml](https://github.com/PRQL/prql/blob/main/Taskfile.yaml),\n  installing dependencies with `cargo`, `brew`, `npm` & `uv`, and suggests some\n  VS Code extensions.\n\n  ```sh\n  task setup-dev\n  ```\n\n### Option 2: Install tools individually\n\n- We'll need `cargo-insta`, to update snapshot tests:\n\n  ```sh\n  cargo install cargo-insta\n  ```\n\n- We'll need Python, which most systems will have already. The easiest way to\n  check is to try running the full tests:\n\n  ```sh\n  cargo test\n  ```\n\n  ...and if that doesn't complete successfully, ensure we have Python >= 3.7, to\n  compile `prqlc-python`.\n\n- For more involved contributions, such as building the website, playground,\n  book, or some release artifacts, we'll need some additional tools. But we\n  won't need those immediately, and the error messages on what's missing should\n  be clear when we attempt those things. When we hit them, the\n  [Taskfile.yaml](https://github.com/PRQL/prql/blob/main/Taskfile.yaml) will be\n  a good source to copy & paste instructions from.\n\n### Option 3: Use a Dev Container\n\nThis project has a\n[devcontainer.json file](https://github.com/PRQL/prql/blob/main/.devcontainer/devcontainer.json)\nand a\n[pre-built dev container base Docker image](https://github.com/PRQL/prql/pkgs/container/prql-devcontainer-base).\nLearn more about Dev Containers at\n[https://containers.dev/](https://containers.dev/)\n\nCurrently, the tools for Rust are already installed in the pre-built image, and,\nNode.js, Python and others are configured to be installed when build the\ncontainer.\n\nWhile there are a variety of tools that support Dev Containers, the focus here\nis on developing with VS Code in a container by\n[GitHub Codespaces](https://docs.github.com/en/codespaces/overview) or\n[VS Code Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).\n\nTo use a Dev Container on a local computer with VS Code, install the\n[VS Code Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)\nand its system requirements. Then refer to the links above to get started.\n\n### Option 4: Use nix development environment\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> This is used by a member of the core team on Linux, but doesn't\n> currently work on Mac. We're open to contributions to improve support.\n\nA [nix](https://nixos.org/) flake `flake.nix` provides 3 development\nenvironments:\n\n- **default**, for building the compiler\n- **web**, for the compiler and the website,\n- **full**, for the compiler, the website and the compiler bindings.\n\nTo load the shell:\n\n1. [Install nix (the package manager)](https://nixos.org/download). (only first\n   time)\n\n2. Enable flakes, which are a (pretty stable) experimental feature of nix. (only\n   first time)\n\n   For non-NixOS users:\n\n   ```sh\n   mkdir -p ~/.config/nix/\n   tee 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf\n   ```\n\n   For NixOS users, follow instructions [here](https://nixos.wiki/wiki/Flakes).\n\n3. Run:\n\n   ```sh\n   nix develop\n   ```\n\n   To use the \"web\" or \"full\" shell, run:\n\n   ```sh\n   nix develop .#web\n   ```\n\nOptionally, you can install [direnv](https://direnv.net/), to automatically load\nthe shell when you enter this repo. The easiest way is to also install\n[direnv-nix](https://github.com/nix-community/nix-direnv) and configure your\n`.envrc` with:\n\n```sh\n# .envrc\nuse flake .#full\n```\n\n---\n\n## Contribution workflow\n\nWe're similar to most projects on GitHub — open a Pull Request with a suggested\nchange!\n\n### Commits\n\n- If a change is user-facing, please add a line in\n  [**`CHANGELOG.md`**](https://github.com/PRQL/prql/blob/main/CHANGELOG.md),\n  with `{message}, ({@contributor, #X})` where `X` is the PR number.\n  - If there's a missing entry, a follow-up PR containing just the changelog\n    entry is welcome.\n- We're using [Conventional Commits](https://www.conventionalcommits.org)\n  message format, enforced through\n  [action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request).\n\n### Merges\n\n- **We merge any code that makes PRQL better**\n- A PR doesn't need to be perfect to be merged; it doesn't need to solve a big\n  problem. It needs to:\n  - be in the right direction,\n  - make incremental progress,\n  - be explicit on its current state, so others can continue the progress.\n- That said, there are a few instances when we need to ensure we have some\n  consensus before merging code — for example non-trivial changes to the\n  language, or large refactorings to the library.\n- If you have merge permissions, and are reasonably confident that a PR is\n  suitable to merge (whether or not you're the author), feel free to merge.\n  - If you don't have merge permissions and have authored a few PRs, ask and ye\n    shall receive.\n- The primary way we ratchet the code quality is through automated tests.\n  - This means PRs almost always need a test to demonstrate incremental\n    progress.\n  - If a change breaks functionality without breaking tests, our tests were\n    probably insufficient.\n  - If a change breaks existing tests (for example, changing an external API),\n    that indicates we should be careful about merging a change, including\n    soliciting others' views.\n- We use PR reviews to give general context, offer specific assistance, and\n  collaborate on larger decisions.\n  - Reviews around 'nits' like code formatting / idioms / etc are very welcome.\n    But the norm is for them to be received as helpful advice, rather than as\n    mandatory tasks to complete. Adding automated tests & lints to automate\n    these suggestions is welcome.\n  - If you have merge permissions and would like a PR to be reviewed before it\n    merges, that's great — ask or assign a reviewer.\n  - If a PR hasn't received attention after a day, please feel free to ping the\n    pull request.\n- People may review a PR after it's merged. As part of the understanding that we\n  can merge quickly, contributors are expected to incorporate substantive\n  feedback into a future PR.\n- We should revert quickly if the impact of a PR turns out not to be consistent\n  with our expectations, or there isn't as much consensus on a decision as we\n  had hoped. It's very easy to revert code and then re-revert when we've\n  resolved the issue; it's a sign of moving quickly. Other options which resolve\n  issues immediately are also fine, such as commenting out an incorrect test or\n  adding a quick fix for the underlying issue.\n\n## Docs\n\nWe're very keen on contributions to improve our documentation.\n\nThis includes our docs in the book, on the website, in our code, or in a Readme.\nWe also appreciate issues pointing out that our documentation was confusing,\nincorrect, or stale — if it's confusing for you, it's probably confusing for\nothers.\n\nSome principles for ensuring our docs remain maintainable:\n\n- Docs should be as close as possible to the code. Doctests are ideal on this\n  dimension — they're literally very close to the code and they can't drift\n  apart since they're tested on every commit. Or, for example, it's better to\n  add text to a `--help` message, rather than write a paragraph in the Readme\n  explaining the CLI.\n- We should have some visualization of how to maintain docs when we add them.\n  Docs have a habit of falling out of date — the folks reading them are often\n  different from those writing them, they're sparse from the code, generally not\n  possible to test, and are rarely the by-product of other contributions. Docs\n  that are concise & specific are easier to maintain.\n- Docs should be specifically relevant to PRQL; anything else we can instead\n  link to.\n\nIf something doesn't fit into one of these categories, there are still lots of\nways of getting the word out there — a blog post / gist / etc. Let us know and\nwe're happy to link to it / tweet it.\n\n## How we test\n\nWe use a pyramid of tests — we have fast, focused tests at the bottom of the\npyramid, which give us low latency feedback when developing, and then slower,\nbroader tests which ensure that we don't miss anything as PRQL\ndevelops{{footnote: Our approach is very consistent with\n**[@matklad](https://github.com/matklad)**'s advice, in his excellent blog\npost [How to\nTest](https://matklad.github.io//2021/05/31/how-to-test.html).}}.\n\n<!-- markdownlint-disable MD053 -->\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> If you're making your first contribution, you don't need to engage\n> with all this — it's fine to just make a change and push the results; the\n> tests that run in GitHub will point you towards any errors, which can be then\n> be run locally if needed. We're always around to help out.\n\nOur tests, from the bottom of the pyramid to the top:\n\n- **[Static checks](https://github.com/PRQL/prql/blob/main/.pre-commit-config.yaml)**\n  — we run a few static checks to ensure the code stays healthy and consistent.\n  They're defined in\n  [**`.pre-commit-config.yaml`**](https://github.com/PRQL/prql/blob/main/.pre-commit-config.yaml),\n  using [pre-commit](https://pre-commit.com). They can be run locally with\n\n  ```sh\n  task lint\n  # or\n  pre-commit run -a\n  ```\n\n  The tests fix most of the issues they find themselves. Most of them also run\n  on GitHub on every commit; any changes they make are added onto the branch\n  automatically in an additional commit.\n  - Checking by [MegaLinter](https://megalinter.io/latest/), which includes more\n    Linters, is also done automatically on GitHub. (experimental)\n\n- **Unit tests & inline insta snapshots** — we rely on unit tests to rapidly\n  check that our code basically works. We extensively use\n  [Insta](https://insta.rs/), a snapshot testing tool which writes out the\n  values generated by our code, making it fast & simple to write and modify\n  tests{{footnote:\n  [Here's an example of an insta test](https://github.com/PRQL/prql/blob/0.2.2/prql-compiler/src/parser.rs#L580-L605)\n  — note that only the initial line of each test is written by us; the\n  remainder is filled in by insta.}}\n\n  These are the fastest tests which run our code; they're designed to run on\n  every save while you're developing. We include a `task` which does this:\n\n  ```sh\n  task prqlc:test\n  # or\n  cargo insta test --accept --package prqlc --lib\n  ```\n\n<!--\nThis is the previous doc. It has the advantage that it explains what it's doing, and is\neasy to change (e.g. to run all packages). But because of\nhttps://github.com/watchexec/watchexec/issues/371, the ignore behavior is unfortunately quite\ninconsistent in watchexec. Let's revert back if it gets solved.\n\n[^2]: For example, this is a command I frequently run:\n\n    ```sh\n    RUST_BACKTRACE=1 watchexec -e rs,toml,md -cr --ignore='target/**' -- cargo -q insta test --accept -p prqlc --lib\n    ```\n\n    Breaking this down:\n\n    - `RUST_BACKTRACE=1` will print a full backtrace, including where an error\n      value was created, for Rust tests which return `Result`s.\n    - `watchexec -e rs,toml,md -cr --ignore='target/**' --` will run the\n      subsequent command on any change to files with extensions which we are\n      generally editing.\n    - `cargo insta test --accept --` runs tests with `insta`, a snapshot\n      library, and writes any results immediately. I rely on git to track\n      changes, so I run with `--accept`, but YMMV.\n    - `-p prqlc --lib` is passed to cargo by `insta`; `-p prqlc`\n      tells it to only run the tests for `prqlc` rather than the other\n      crates, and `--lib` to only run the unit tests rather than the integration\n      tests, which are slower.\n    - Note that we don't want to re-run on _any_ file changing, because we can\n      get into a loop of writing snapshot files, triggering a change, writing a\n      snapshot file, etc. -->\n\n- **[Documentation](https://github.com/PRQL/prql/tree/main/web/book/tests/documentation)**\n  — we compile all examples from our documentation in the Website, README, and\n  PRQL Book, to test that they produce the SQL we expect, and that changes to\n  our code don't cause any unexpected regressions. These are included in:\n\n  ```sh\n  cargo insta test --accept\n  ```\n\n- **[Database integration tests](https://github.com/PRQL/prql/tree/main/prqlc/prqlc/tests/integration/dbs)**\n  — we run tests with example queries against databases with actual data to\n  ensure we're producing correct SQL across our supported dialects. The\n  in-process tests can be run locally with:\n\n  ```sh\n  task prqlc:test-all\n  # or\n  cargo insta test --accept --features=default,test-dbs\n  ```\n\n  More details on running with external databases are in the\n  [Readme](https://github.com/PRQL/prql/tree/main/prqlc/prqlc/tests/integration/dbs).\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Integration tests use DuckDB, and so require a clang compiler to\n> compile [`duckdb-rs`](https://github.com/wangfenjin/duckdb-rs). Most\n> development systems will have one, but if the test command fails, install a\n> clang compiler with:\n>\n> - On macOS, install xcode with `xcode-select --install`\n> - On Debian Linux, `apt-get update && apt-get install clang`\n> - On Windows, `duckdb-rs` isn't supported, so these tests are excluded\n\n- **[GitHub Actions on every commit](https://github.com/PRQL/prql/blob/main/.github/workflows/tests.yaml)**\n  — we run tests relevant to a PR's changes in CI — for example changes to docs\n  will attempt to build docs, changes to a binding will run that binding's\n  tests. The vast majority of changes trigger tests which run in less than five\n  minutes, and we should be reassessing their scope if they take longer than\n  that. Once these pass, a pull request can be merged.\n\n- **[GitHub Actions on merge](https://github.com/PRQL/prql/blob/c042eef48709e2c1af577161554fd09f14e67e0f/.github/workflows/pull-request.yaml#L124)**\n  — we run a wider set tests on every merge to main. This includes testing\n  across OSs, all our language bindings, a measure of test code coverage, and\n  some performance benchmarks.\n\n  If these tests fail after merging, we should revert the commit before fixing\n  the test and then re-reverting.\n\n  Most of these will run locally with:\n\n  ```sh\n  task test-all\n  ```\n\n- **[GitHub Actions nightly](https://github.com/PRQL/prql/blob/main/.github/workflows/nightly.yaml)**\n  — every night, we run tests that take longer, are less likely to fail, or are\n  unrelated to code changes — such as security checks, bindings' tests on\n  multiple OSs, or expensive timing benchmarks.\n\n  We can run these tests before a merge by adding a label `pr-nightly` to the\n  PR.\n\nThe goal of our tests is to allow us to make changes quickly. If they're making\nit more difficult to make changes, or there are missing tests that would offer\nthe confidence to make changes faster, please raise an issue.\n\n---\n\n## Website\n\nThe website is published together with the book and the playground, and is\nautomatically built and released on any push to the `web` branch.\n\nThe `web` branch points to the latest release plus any website-specific fixes.\nThat way, the compiler behavior in the playground matches the latest release\nwhile allowing us to fix mistakes in the docs with a tighter loop than every\nrelease.\n\nFixes to the playground, book, or website should have a `pr-backport-web` label\nadded to their PR — a bot will then open & merge another PR onto the `web`\nbranch once the initial branch merges.\n\nThe website components will run locally with:\n\n```sh\n# Run the main website\ntask web:run-website\n# Run the PRQL online book\ntask web:run-book\n# Run the PRQL playground\ntask web:run-playground\n```\n\n## Bindings\n\nWe have a number of language bindings, as documented at\n<https://prql-lang.org/book/project/bindings/index.html>. Some of these are\nwithin our monorepo, some are in separate repos. Here's a provisional framework\nfor when we use the main prql repo vs separate repos for bindings:\n\n| Factor                                           | Rationale                                                                                  | Example                                                       |\n| ------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------- |\n| Does someone want to sign up to maintain a repo? | A different repo is harder for the core team to maintain                                   | `tree-sitter-prql` is well maintained                         |\n| Can it change independently from the compiler?   | If it's in a different repo, it can't be changed in lockstep with the compiler             | `prql-vscode` is fine to change \"behind\" the language         |\n| Would a separate repo invite new contributors?   | A monorepo with all the rust code can be less inviting for those familiar with other langs | `prql-vscode` had some JS-only contributors                   |\n| Is there an convention for a stand-alone repo?   | A small number of ecosystems require a separate repo                                       | `homebrew-prql` needs to be named that way for a Homebrew tap |\n\n---\n\n## Releasing\n\nCurrently we release in a semi-automated way:\n\n1. PR & merge an updated\n   [Changelog](https://github.com/PRQL/prql/blob/main/CHANGELOG.md). GitHub will\n   produce a draft version at <https://github.com/PRQL/prql/releases/new>,\n   including \"New Contributors\".\n\n   Use this script to generate a line introducing the enumerated changes:\n\n   ```sh\n   echo \"It has $(git rev-list --count $(git rev-list --tags --max-count=1)..) commits from $(git shortlog --summary $(git rev-list --tags --max-count=1).. | wc -l | tr -d '[:space:]') contributors. Selected changes:\"\n   ```\n\n   When a fix closes an issue reported by someone other than the PR author,\n   thank them in the changelog entry, e.g.\n   `(@pr-author, #5639; reported by @issue-reporter)`.\n\n2. If the current version is correct, then skip ahead. But if the version needs\n   to be changed — for example, we had planned on a patch release, but instead\n   require a minor release — then run\n   `cargo release version $version -x && cargo release replace -x && task prqlc:test-all`\n   to bump the version, and PR the resulting commit.\n\n3. Ensure all changes intended for the release are merged to `main`. Then create\n   the release (which creates the tag on the latest commit on `main`):\n\n   **Web UI:** Go to\n   [Draft a new release](https://github.com/PRQL/prql/releases/new){{footnote: Only\n       maintainers have access to this page.}},\n   copy the changelog entry into the release\n   description{{footnote: Unfortunately GitHub's markdown parser\n        interprets linebreaks as newlines. I haven't found a better way of\n        editing the markdown to look reasonable than manually editing the text\n        or asking LLM to help.}}, enter the tag to be created, and hit\n   \"Publish\".\n\n   **CLI:**\n\n   ```sh\n   gh release create $version --title \"$version\" --notes \"$(cat <<'EOF'\n   <paste changelog entry here>\n   EOF\n   )\"\n   ```\n\n4. From there, both the tag and release is created and all packages are\n   published automatically based on our\n   [release workflow](https://github.com/PRQL/prql/blob/main/.github/workflows/release.yaml).\n\n5. Run\n   `cargo release patch --no-publish --no-push --execute --no-verify --no-confirm --no-tag && task prqlc:test-all`\n   to bump the versions and add a new Changelog section; then PR the resulting\n   commit. Note this currently contains `task prqlc:test-all` to update snapshot\n   tests which contain the version.\n\n<!-- Note we used to have `cargo release version patch -x --no-confirm && cargo release replace -x --no-confirm && task prqlc:test-all`, which was simpler, but in order for `prev_version` to work, we can't separate the `patch` and `replace`, and we need `prev_version` for the prqlc version constraint (search for `prev_version` if unclear). If we moved back to upgrading the tags at the time of release rather than after, we could go back to that. -->\n\n6. Check whether there are [milestones](https://github.com/PRQL/prql/milestones)\n   that need to be pushed out.\n\n7. Review the **Current Status** on the README.md to ensure it reflects the\n   project state.\n\nWe may make this more automated in future; e.g. automatic changelog creation.\n"
  },
  {
    "path": "web/book/src/project/contributing/fortnightly-dev-call.ics",
    "content": "BEGIN:VCALENDAR\nPRODID:-//Apple Inc.//macOS 12.6.1//EN\nVERSION:2.0\nBEGIN:VEVENT\nDTSTAMP:20240107T160200\nUID:dev-call.prql-lang.org\nDTSTART;TZID=America/New_York:20240107T133000\nDTEND;TZID=America/New_York:20240107T140000\nRRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1SU,3SU;UNTIL=20250331\nDESCRIPTION:Discord #dev-voice-channel\\nhttps://discord.gg/2RUBzz3R3J\\n1730Z on 1st & 3rd Sunday\nLOCATION:https://discord.gg/2RUBzz3R3J\\n1730Z\nSEQUENCE:6\nSTATUS:CONFIRMED\nSUMMARY:PRQL Fortnightly Dev Call\nTRANSP:TRANSPARENT\nEND:VEVENT\nEND:VCALENDAR\n"
  },
  {
    "path": "web/book/src/project/contributing/language-design.md",
    "content": "# Language design\n\nIn a way PRQL is just a transpiler to SQL. This can cause its language design to\ngravitate toward thinking about PRQL features in terms of how they translate to\nSQL.\n\n```\nPRQL feature -> SQL feature -> relational result\n```\n\nThis is flawed because:\n\n- it does not model interactions between features well,\n- SQL behavior can sometimes be misleading (the order of a subquery will not\n  persist in the parent query) or even differs between dialects (set\n  operations).\n\nInstead, we should think of PRQL features in terms of how they affect PRQL\nexpressions, which in most cases means how they affect relations.\n\n```\nPRQL feature -> relation\n                   |\n                   v\nPRQL feature -> relation\n                   |\n                   v\nPRQL feature -> relation\n                   |\n                   v\n            relational result\n```\n\nThinking about SQL comes in only at the last step when relation (or rather\nrelational expression) is translated to an SQL expression.\n"
  },
  {
    "path": "web/book/src/project/integrations/README.md",
    "content": "# Integrations\n\nPRQL is building integrations with lots of external tools, including:\n\n- [`prqlc` CLI](./prqlc-cli.md) - Rust compiler for the command line\n- [ClickHouse](./clickhouse.md)\n- [Jupyter](./jupyter.md)\n- [DuckDB](./duckdb.md)\n- [QStudio](./qstudio.md)\n- [Prefect](./prefect.md)\n- [VS Code](./vscode.md)\n- [PostgreSQL](./postgresql.md)\n- [Databend](./databend.md)\n- [Rill](./rill.md)\n- [Syntax highlighting](./syntax-highlighting.md) for many popular tools.\n"
  },
  {
    "path": "web/book/src/project/integrations/clickhouse.md",
    "content": "# ClickHouse\n\nPRQL works natively in ClickHouse. Check out the\n[ClickHouse docs](https://clickhouse.com/docs/en/guides/developer/alternative-query-languages)\nfor more details.\n"
  },
  {
    "path": "web/book/src/project/integrations/databend.md",
    "content": "# Databend\n\nDatabend natively supports PRQL. For more details see the\n[databend docs](https://www.databend.com/blog/2024-04-03-databend-integrates-prql/).\n"
  },
  {
    "path": "web/book/src/project/integrations/duckdb.md",
    "content": "# DuckDB\n\nThere's a [DuckDB](https://duckdb.org/) community extension by\n**[@ywelsch](https://github.com/ywelsch)** at the DuckDB Community Extension\nRepository.\n\n```sql\nINSTALL prql FROM community;\nLOAD prql;\n-- Once the extension is loaded, you can write PRQL queries\nfrom (read_csv 'https://raw.githubusercontent.com/PRQL/prql/0.8.0/prql-compiler/tests/integration/data/chinook/invoices.csv')\nfilter invoice_date >= @2009-02-01\ntake 5;\n```\n\nCheck out the\n[extension's documentation](https://community-extensions.duckdb.org/extensions/prql.html)\nfor more details.\n"
  },
  {
    "path": "web/book/src/project/integrations/jupyter.md",
    "content": "# Jupyter\n\n[pyprql](https://pypi.org/project/pyprql/) contains `pyprql.magic`, a thin\nwrapper of [`JupySQL`](https://pypi.org/project/jupysql/)'s SQL IPython magics.\nThis allows us to run PRQL interactively on Jupyter/IPython.\n\nCheck out <https://pyprql.readthedocs.io/> for more context.\n\n## Installation\n\n```sh\npip install pyprql\n```\n\n## Usage\n\nWhen installing pyprql, the\n[duckdb-engine](https://pypi.org/project/duckdb-engine/) package is also\ninstalled with it, so we can start using PRQL immediately to query CSV and\nParquet files.\n\nFor example, running\n[the example from the JupySQL documentation](https://jupysql.ploomber.io/en/latest/quick-start.html)\non IPython:\n\n```python\nIn [1]: %load_ext pyprql.magic\n\nIn [2]: !curl -sL https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv -o penguins.csv\n\nIn [3]: %prql duckdb://\n\nIn [4]: %prql from `penguins.csv` | take 3\nOut[4]:\n  species     island  bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g     sex\n0  Adelie  Torgersen            39.1           18.7                181         3750    MALE\n1  Adelie  Torgersen            39.5           17.4                186         3800  FEMALE\n2  Adelie  Torgersen            40.3           18.0                195         3250  FEMALE\n\nIn [5]: %%prql\n   ...: from `penguins.csv`\n   ...: filter bill_length_mm > 40\n   ...: take 3\n   ...:\n   ...:\nOut[5]:\n  species     island  bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g     sex\n0  Adelie  Torgersen            40.3           18.0                195         3250  FEMALE\n1  Adelie  Torgersen            42.0           20.2                190         4250    None\n2  Adelie  Torgersen            41.1           17.6                182         3200  FEMALE\n```\n"
  },
  {
    "path": "web/book/src/project/integrations/postgresql.md",
    "content": "# PostgreSQL\n\nPL/PRQL is a PostgreSQL extension that lets you write functions with PRQL.\n\nPL/PRQL functions serve as intermediaries, compiling the user's PRQL code into\nSQL statements that PostgreSQL executes. The extension is based on the\n[pgrx](https://github.com/pgcentralfoundation/pgrx) framework for developing\nPostgreSQL extensions in Rust. This framework manages the interaction with\nPostgreSQL's internal APIs, type conversions, and other function hooks necessary\nto integrate PRQL with PostgreSQL.\n\n## Examples\n\nPL/PRQL functions are defined using the `plprql` language specifier:\n\n```sql\ncreate function match_stats(int) returns table(player text, kd_ratio float) as $$\n  from matches\n  filter match_id == $1\n  group player (\n    aggregate {\n      total_kills = sum kills,\n      total_deaths = sum deaths\n    }\n  )\n  filter total_deaths > 0\n  derive kd_ratio = total_kills / total_deaths\n  select { player, kd_ratio }\n$$ language plprql;\n\nselect * from match_stats(1001)\n\n player  | kd_ratio\n---------+----------\n Player1 |    0.625\n Player2 |      1.6\n(2 rows)\n```\n\nYou can also run PRQL code directly with the `prql` function which is useful for\ncustom SQL in ORMs:\n\n```sql\nselect prql('from matches | filter player == ''Player1''')\nas (id int, match_id int, round int, player text, kills int, deaths int)\nlimit 2;\n\n id | match_id | round | player  | kills | deaths\n----+----------+-------+---------+-------+--------\n  1 |     1001 |     1 | Player1 |     4 |      1\n  3 |     1001 |     2 | Player1 |     1 |      7\n(2 rows)\n\n-- Same as above without the need for the static types, but returns cursor\nselect prql('from matches | filter player == ''Player1''', 'player1_cursor');\nfetch 2 from player1_cursor;\n```\n\n## Getting Started\n\nFor installation instructions and more information on the extension, see the\n[PL/PRQL repository](https://github.com/kaspermarstal/plprql).\n"
  },
  {
    "path": "web/book/src/project/integrations/prefect.md",
    "content": "# Prefect\n\nBecause [Prefect](https://www.prefect.io/) is in native Python, it's extremely\neasy to integrate with PRQL.\n\nWith a Postgres Task, replace:\n\n```python\nPostgresExecute.run(..., query=sql)\n```\n\n...with...\n\n```python\nPostgresExecute.run(..., query=prql_python.compile(prql))\n```\n\nWe're big fans of Prefect, and if there is anything that would make the\nintegration easier, please open an issue.\n"
  },
  {
    "path": "web/book/src/project/integrations/prqlc-cli.md",
    "content": "{{#include ../../../../../prqlc/prqlc/README.md}}\n"
  },
  {
    "path": "web/book/src/project/integrations/qstudio.md",
    "content": "# QStudio IDE\n\n[QStudio](https://www.timestored.com/qstudio/prql-ide) is a SQL GUI that lets\nyou browse tables, run SQL scripts, and chart and export the results. QStudio\nruns on Windows, macOS and Linux, and works with every popular database\nincluding mysql, postgresql, mssql, kdb....\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> QStudio relies on the PRQL compiler. You must ensure that `prqlc` is\n> in your path. See the\n> [installation instructions](https://prql-lang.org/book/project/integrations/prqlc-cli.html#installation)\n> for `prqlc` in the PRQL reference guide for details.\n\nQStudio calls `prqlc` (the PRQL compiler) to generate SQL code from PRQL queries\n(.prql files) then runs the SQL against the selected database to display the\nresults. For more details, check out:\n\n- [QStudio site](https://www.timestored.com/qstudio/prql-ide)\n- [QStudio-PRQL Quick Start](https://github.com/richb-hanover/qStudio-PRQL_Quick_Start)\n- There is a\n  [double-clickable macOS app](https://randomneuronsfiring.com/wp-content/uploads/QStudio.zip)\n  that bundles QStudio and the `prqlc` compiler.\n"
  },
  {
    "path": "web/book/src/project/integrations/rill.md",
    "content": "# Rill\n\nPRQL has had some work to integrate with Rill. See the\n[Rill issues](https://github.com/PRQL/prql/issues?q=is%3Aissue+rill) for more\ndetails.\n"
  },
  {
    "path": "web/book/src/project/integrations/syntax-highlighting.md",
    "content": "{{#include ../../../../../grammars/README.md}}\n\n---\n\nSince the [Elm](https://elm-lang.org/) language coincidentally provides syntax\nhighlighting suitable for PRQL, it may look better to mark PRQL code as Elm when\nthe above definition files are not available.\n\nFor example, the following Markdown code block will be nicely highlighted on\nGitHub, Pandoc, and other Markdown renderers:\n\n````markdown\n```elm\nfrom employees\nfilter start_date > @2021-01-01\n```\n````\n\nWe hope that in the future these renderers will recognize PRQL code blocks and\nhave syntax highlighting applied, and we are tracking these with several issues.\n\n- GitHub (Linguist): <https://github.com/PRQL/prql/issues/1636>\n- Pandoc (Kate): <https://github.com/PRQL/prql/issues/2213>\n"
  },
  {
    "path": "web/book/src/project/integrations/vscode.md",
    "content": "# Visual Studio Code extension\n\nPRQL has a Visual Studio Code extension that compiles a PRQL query in a VS Code\neditor and displays the resulting SQL code in a second pane on the side. This is\nvery handy for editing, saving, and reusing PRQL queries in VS Code.\n\nTo install the VS Code extension, open VS Code and type\n<kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>P</kbd>\n(<kbd>Cmd</kbd>-<kbd>Shift</kbd>-<kbd>P</kbd> on a Mac) and type `PRQL`. Install\nthe extension as usual.\n\n[Repo for the PRQL VS Code extension](https://github.com/PRQL/prql-vscode)\n\n[Extension on VS Marketplace](https://marketplace.visualstudio.com/items?itemName=PRQL-lang.prql-vscode)\n"
  },
  {
    "path": "web/book/src/project/target.md",
    "content": "# Target & Version\n\n## Target dialect\n\nPRQL allows specifying a target dialect at the top of the query, which allows\nPRQL to compile to a database-specific SQL flavor.\n\n### Examples\n\n```prql\nprql target:sql.postgres\n\nfrom employees\nsort age\ntake 10\n```\n\n```prql\nprql target:sql.mssql\n\nfrom employees\nsort age\ntake 10\n```\n\n## Dialects\n\n### Supported\n\nSupported dialects support all PRQL language features where possible, are tested\non every commit, and we'll endeavor to fix bugs.\n\n- `sql.clickhouse`\n- `sql.duckdb`\n- `sql.generic`\n  {{footnote: while there's no \"generic\" DB to test `sql.generic` against, we still count it as supported.}}\n- `sql.glaredb`\n- `sql.mysql`\n- `sql.postgres`\n- `sql.sqlite`\n\n### Unsupported\n\nUnsupported dialects have implementations in the compiler, but are tested\nminimally or not at all, and may have gaps for some features.\n\nWe're open to contributions to improve our coverage of these, and to adding\nadditional dialects.\n\n- `sql.mssql`\n- `sql.ansi`\n- `sql.bigquery`\n- `sql.snowflake`\n\n## Priority of targets\n\nThe compile target of a query is defined in the query's header or as an argument\nto the compiler. option. The argument to the compiler takes precedence.\n\nFor example, the following shell example specifies `sql.generic` in the query\nand `sql.duckdb` in the `--target` option of the `prqlc compile` command. In\nthis case, `sql.duckdb` takes precedence and the SQL output is based on the\nDuckDB dialect.\n\n```sh\necho 'prql target:sql.generic\n      from foo' | prqlc compile --target sql.duckdb\n```\n\nTo use the target described in the query, a special target `sql.any` can be\nspecified in the compiler option.\n\n```sh\necho 'prql target:sql.generic\n      from foo' | prqlc compile --target sql.any\n```\n\n## Version\n\nPRQL allows specifying a version of the language in the PRQL header, like:\n\n```prql\nprql version:\"0.13.12\"\n\nfrom employees\n```\n\nThis has two roles, one of which is implemented:\n\n- The compiler will raise an error if the compiler is older than the query\n  version. This prevents confusing errors when queries use newer features of the\n  language but the compiler hasn't yet been upgraded.\n- The compiler will compile for the major version of the query. This allows the\n  language to evolve without breaking existing queries, or forcing multiple\n  installations of the compiler. This isn't yet implemented, but is a gating\n  feature for PRQL 1.0.\n\nThe version of the compiler currently in use can be called using the special\nfunction `std.prql.version` in PRQL.\n\n```prql\n[{version = prql.version}]\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> This function was renamed from `std.prql_version` to `prql.version` in\n> PRQL 0.11.1. `std.prql_version` will be removed in PRQL 0.12.0.\n"
  },
  {
    "path": "web/book/src/reference/data/README.md",
    "content": "# Importing data\n"
  },
  {
    "path": "web/book/src/reference/data/from.md",
    "content": "# From\n\nSpecifies a data source.\n\n```prql\nfrom artists\n```\n\nTo introduce an alias, use an assign expression:\n\n```prql\nfrom e = employees\nselect e.first_name\n```\n\nTable names containing spaces or special characters\n[need to be contained within backticks](../syntax/keywords.md#quoting):\n\n```prql\nfrom `artist tracks`\n```\n\n`default_db.tablename` can be used if the table name matches a function from the\nstandard library.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> We realize this is an awkward workaround. Track & 👍\n> [#3271](https://github.com/PRQL/prql/issues/3271) for resolving this.\n\n```prql\ndefault_db.group  # in place of `from group`\ntake 1\n```\n"
  },
  {
    "path": "web/book/src/reference/data/read-files.md",
    "content": "# Reading files\n\nThere are a few functions mainly designed for DuckDB to read from files:\n\n```prql\nprql target:sql.duckdb\n\nfrom a = (read_parquet \"artists.parquet\")\njoin b = (read_csv \"albums.csv\") (a.artist_id == b.artist_id)\njoin c = (read_json \"metadata.json\") (a.artist_id == c.artist_id)\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> These don't currently have all the DuckDB options. If those would be\n> helpful, please log an issue and it's a fairly easy addition.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> We may be able to reduce the boilerplate\n> `WITH table_x AS SELECT * FROM...` in future versions.\n\nWhen specifying file names directly in the `FROM` clause without using\nfunctions, which is allowed in DuckDB, enclose the file names in backticks\n` `` ` as follows:\n\n```prql\nfrom `artists.parquet`\n```\n\n## See also\n\n- [Target and Version](../../project/target.md)\n- [Ad-hoc data](./relation-literals.md)\n"
  },
  {
    "path": "web/book/src/reference/data/relation-literals.md",
    "content": "# How do I: create ad-hoc relations?\n\nIt's often useful to make a small inline relation, for example when exploring\nhow a database will evaluate an expression, or for a small lookup table. This\ncan be quite verbose in SQL.\n\nPRQL offers two approaches — array literals, and a `from_text` transform.\n\n## Array literals\n\nBecause relations (aka a table) in PRQL are just arrays of tuples, they can be\nexpressed with array and tuple syntax:\n\n```prql\nfrom [\n  {a=5, b=false},\n  {a=6, b=true},\n]\nfilter b == true\nselect a\n```\n\n```prql\nlet my_artists = [\n  {artist=\"Miles Davis\"},\n  {artist=\"Marvin Gaye\"},\n  {artist=\"James Brown\"},\n]\n\nfrom artists\njoin my_artists (==artist)\njoin albums (==artist_id)\nselect {artists.artist_id, albums.title}\n```\n\n## `from_text`\n\n`from_text` takes a string in a common format, and converts it to table. It\naccepts a few formats:\n\n- `format:csv` parses CSV (default),\n\n- `format:json` parses either:\n  - an array of objects each of which represents a row, or\n\n  - an object with fields `columns` & `data`, where `columns` take an array of\n    column names and `data` takes an array of arrays.\n\n```prql\nfrom_text \"\"\"\na,b,c\n1,2,3\n4,5,6\n\"\"\"\nderive {\n    d = b + c,\n    answer = 20 * 2 + 2,\n}\n```\n\n```prql\nfrom_text format:json \"\"\"\n[\n    {\"a\": 1, \"m\": \"5\"},\n    {\"a\": 4, \"n\": \"6\"}\n]\n\"\"\"\n```\n\n```prql\nfrom_text format:json \"\"\"\n{\n    \"columns\": [\"a\", \"b\", \"c\"],\n    \"data\": [\n        [1, \"x\", false],\n        [4, \"y\", null]\n    ]\n}\n\"\"\"\n```\n\n## See also\n\n- [How do I: read files?](./read-files.md)\n"
  },
  {
    "path": "web/book/src/reference/declarations/README.md",
    "content": "# Declarations\n"
  },
  {
    "path": "web/book/src/reference/declarations/functions.md",
    "content": "# Functions\n\n<!--\nTODOs:\n- Examples are a bit artificial — the interp is just \"divide by 100\" in one case!  -->\n\nFunctions have two types of parameters:\n\n1. Positional parameters, which require an argument.\n2. Named parameters, which optionally take an argument, otherwise using their\n   default value.\n\nSo this function is named `fahrenheit_to_celsius` and has one parameter `temp`:\n\n```prql\nlet fahrenheit_to_celsius = temp -> (temp - 32) / 1.8\n\nfrom cities\nderive temp_c = (fahrenheit_to_celsius temp_f)\n```\n\nThe function below is named `interp`, and has two positional parameters named\n`high` and `x`, and one named parameter named `low` which takes a default\nargument of `0`. It calculates the proportion of the distance that `x` is\nbetween `low` and `high`.\n\n```prql\nlet interp = low:0 high x -> (x - low) / (high - low)\n\nfrom students\nderive {\n  sat_proportion_1 = (interp 1600 sat_score),\n  sat_proportion_2 = (interp low:0 1600 sat_score),\n}\n```\n\n## Other examples\n\n```prql\nlet is_adult = col -> col >= 18\nlet writes_code = col -> (col | in [\"PRQL\", \"Rust\"])\nlet square = col -> (col | math.pow 2)\nlet starts_with_a = col -> (col | text.lower | text.starts_with(\"a\"))\n\nfrom employees\nselect {\n    first_name,\n    last_name,\n    hobby,\n    adult = is_adult age,\n    age_squared = square age,\n}\nfilter ((starts_with_a last_name) && (writes_code hobby))\n```\n\n## Piping values into functions\n\nConsistent with the principles of PRQL, it's possible to pipe values into\nfunctions, which makes composing many functions more readable. When piping a\nvalue into a function, the value is passed as an argument to the final\npositional parameter of the function. Here's the same result as the examples\nabove with an alternative construction:\n\n```prql\nlet interp = low:0 high x -> (x - low) / (high - low)\n\nfrom students\nderive {\n  sat_proportion_1 = (sat_score | interp 1600),\n  sat_proportion_2 = (sat_score | interp low:0 1600),\n}\n```\n\nand\n\n```prql\nlet fahrenheit_to_celsius = temp -> (temp - 32) / 1.8\n\nfrom cities\nderive temp_c = (temp_f | fahrenheit_to_celsius)\n```\n\nWe can combine a chain of functions, which makes logic more readable:\n\n```prql\nlet fahrenheit_to_celsius = temp -> (temp - 32) / 1.8\nlet interp = low:0 high x -> (x - low) / (high - low)\n\nfrom kettles\nderive boiling_proportion = (temp_c | fahrenheit_to_celsius | interp 100)\n```\n\n### Late binding\n\nFunctions can bind to any variable that is in scope when the function is\nexecuted. For example, here `cost_total` refers to the column that's introduced\nin the `from`.\n\n```prql\nlet cost_share = cost -> cost / cost_total\n\nfrom costs\nselect {materials, labor, overhead, cost_total}\nderive {\n  materials_share = (cost_share materials),\n  labor_share = (cost_share labor),\n  overhead_share = (cost_share overhead),\n}\n```\n\n## Partial application\n\nFunctions can be partially applied, which is useful for creating reusable\ntransform wrappers. When a function returns a partially-applied transform, the\nmissing parameters are automatically propagated.\n\nFor example, we can create a `top_n` function that wraps `take`:\n\n```prql\nlet top_n = n -> take n\n\nfrom invoices\ntop_n 10\n```\n\nThis works because `take` requires two arguments (the count and the relation),\nbut `top_n` only provides one. The relation parameter is automatically filled in\nfrom the pipeline.\n\nWe can also compose multiple partial applications:\n\n```prql\nlet top_n = n -> take n\nlet add_constant = x -> derive {constant = x}\n\nlet my_pipeline = (top_n 5 | add_constant 42)\n\nfrom invoices\nmy_pipeline\n```\n\nOr store a fully-configured transform for reuse:\n\n```prql\nlet top_n = n -> take n\nlet top_5 = top_n 5\n\nfrom invoices\ntop_5\n```\n"
  },
  {
    "path": "web/book/src/reference/declarations/variables.md",
    "content": "# Variables — `let` & `into`\n\nVariables assign a name — say `x` — to an expression, like in most programming\nlanguages. The name can then be used in any expression, acting as a substitute\nfor the expression `x`.\n\nSyntactically, variables can take 3 forms.\n\n- `let` declares the name before the expression.\n\n  ```prql no-eval\n  let my_name = x\n  ```\n\n- `into` declares the name after the expression. This form is useful for quick\n  pipeline splitting and conforms with the \"flow from top to bottom\" rule of\n  pipelines.\n\n  ```prql no-eval\n  x\n  into my_name\n  ```\n\n- The final expression of a pipeline defaults to taking the name `main`.\n\n  ```prql no-eval\n  from x\n  ```\n\n  ... is equivalent to:\n\n  ```prql no-eval\n  let main = x\n  ```\n\nWhen compiling to SQL, relational variables are compiled to Common Table\nExpressions (or sub-queries in some cases).\n\n```prql\nlet top_50 = (\n  from employees\n  sort salary\n  take 50\n  aggregate {total_salary = sum salary}\n)\n\nfrom top_50      # Starts a new pipeline\n```\n\n```prql\nfrom employees\ntake 50\ninto first_50\n\nfrom first_50\n```\n\nVariables can be assigned an s-string containing the whole SQL query\n[s-string](../syntax/s-strings.md), enabling us to use features which PRQL\ndoesn't yet support.\n\n```prql\nlet grouping = s\"\"\"\n  SELECT SUM(a)\n  FROM tbl\n  GROUP BY\n    GROUPING SETS\n    ((b, c, d), (d), (b, d))\n\"\"\"\n\nfrom grouping\n```\n"
  },
  {
    "path": "web/book/src/reference/spec/README.md",
    "content": "# Specification\n\nThis chapter explains PRQL's semantics: how expressions are interpreted and\ntheir meaning. It's intended for advanced users and compiler contributors.\n"
  },
  {
    "path": "web/book/src/reference/spec/modules.md",
    "content": "# Modules\n\n<!-- prettier-ignore -->\n> [!WARNING]\n> The `module` facility is in discussion. This page documents our\n> understanding of the way the final PRQL compiler will likely work. The PRQL\n> compiler currently uses these techniques to compile the `std`, `date`, `text`,\n> and `math` modules into the language.\n>\n> However, at this time (Spring 2024), the `module` facility does not work\n> within a PRQL query itself. That is, a `module` statement in a query cannot\n> import files from the local file system.\n\nDesign goals for **modules**:\n\n1. Allow importing declarations from other files.\n\n2. Have namespaces for things like `std`.\n\n3. Have a hierarchical structure so we can represent files in directories.\n\n4. Have an unambiguous module structure within a project.\n\n## Definition\n\nA module is a namespace that contains declarations. A module is itself a\ndeclaration, which means that it can contain nested child modules.\n\nThis means that modules form a\n[tree graph](<https://en.wikipedia.org/wiki/Tree_(graph_theory)>), which we call\n\"the module structure\".\n\nFor the sake of this document, we will express the module structure with\n`module` keyword and a code block encased in curly braces:\n\n```\nmodule my_playlists {\n    let bangers = ... # a declaration\n\n    module soundtracks {\n        let movie_albums = ... # another declaration\n    }\n}\n```\n\n> The syntax `module name { ...decls... }` is not part of PRQL language, with\n> the objection that it is unnecessary as it only adds more ways of defining\n> modules. If a significant upside of this syntax is found, it may be added in\n> the future.\n\n## Name resolution\n\nAny declarations within a module can be referenced from the outside of the\nmodule:\n\n```prql no-eval\n# using module structure declared above\nmodule my_playlists\n\nlet great_tracks = my_playlists.bangers\n\nlet movie_scores = my_playlists.soundtracks.movie_albums\n```\n\nIdentifiers are resolved relative to current module.\n\n```prql no-eval\nmodule my_playlists {\n    module soundtracks {\n        let movie_albums = (from albums | filter id == 3)\n    }\n\n    from soundtracks.movie_albums\n}\nfrom my_playlists.soundtracks.movie_albums\n```\n\nIf an identifier cannot be resolved relative to the current module, it tries to\nresolve relative to the parent module. This is repeated, stepping up the module\nhierarchy until a match is found or root of the module structure is reached.\n\n```prql no-eval\nmodule my_playlists {\n    let decl_1 = ...\n\n    module soundtracks {\n        let decl_2 = ...\n    }\n\n    module upbeat_rock {\n        let decl_3 = ...\n\n        from decl_1 | join soundtracks.decl2 | join decl_3\n    }\n}\n```\n\n## Main var declaration\n\nThe final variable declaration in a module can omit the leading `let main =` and\nacquire an implicit name main.\n\n```\nmodule my_playlists {\n    let bangers = (from tracks | take 10)\n\n    from playlists | take 10\n}\n\nlet album_titles = my_playlists.main\n```\n\nWhen a module is referenced as a value, the `main` variable is used instead.\nThis is especially useful when referring to a module which is to be compiled to\nRQ (and later SQL).\n\n```\n# last line from previous example could thus be shortened to:\nlet album_titles = my_playlists\n```\n\n## File importing\n\n<!-- prettier-ignore -->\n> [!WARNING]\n> The examples below do **not** work. At this time (Spring 2024), the\n> `module` facility does not work within a PRQL query itself. That is, a\n> `module` statement in a query cannot import files from the local file system.\n\nTo include PRQL source code from other files, we can use the following syntax:\n\n```\nmodule my_playlists\n```\n\nThis loads either `./my_playlists.prql` (a leaf module) or\n`./my_playlists/_my_playlists.prql` (a directory module) and uses its contents\nas module `my_playlists`. If none or both of the files are present, a\ncompilation error is raised.\n\nOnly directory modules can contain module declarations. If a leaf module\ncontains a module declaration, a compilation error is raised, suggesting the\nleaf module to be converted into a directory module. This is a step toward any\nmodule structure having a single \"normalized\" representation in the file system.\nSuch normalization is desired because it restrains the possible file system\nlayouts to a comprehensible and predictable layout, while not sacrificing any\nfunctionality.\n\nDescribed importing rules don't achieve this \"single normalized representation\"\nin full, since any leaf modules could be replaced by a directory module with\nzero submodules, without any semantic changes. Restricting directory modules to\nhave at least one sub-module would not improve approachability enough to justify\nadding this restriction.\n\nFor example, the following module structure is annotated with files names in\nwhich the modules would reside:\n\n```prql no-eval\n\nmodule my_project {\n    # _my_project.prql\n\n    module sales {\n        # sales.prql\n    }\n\n    module projections {\n        # projections/_projections.prql\n\n        module year_2023 {\n            # projections/year_2023.prql\n        }\n\n        module year_2024 {\n            # projections/year_2024.prql\n        }\n    }\n}\n```\n\nIf module `my_project.sales` wants to add a submodule `util`, it has to be\nconverted to a directory modules. This means that it has to be moved to\n`sales/_sales.prql`. The new module would reside in `sales/util.prql`.\n\nThe annotated layout is not the only possible layout for this module structure,\nsince any of the modules `sales`, `year_2023` or `year_2024` could be converted\ninto a directory module with zero sub-modules.\n\nPoint 4 of design goals means that each declaration within a project has a\nsingle fully-qualified name within this project. This is ensured by strict rules\nregarding importing files and the fact that the module structure is a tree.\n\n## Declaration order\n\nThe order of declarations in a module holds no semantic value, except the \"last\n`main` variable\".\n\nReferences between modules can be cyclic.\n\n```\nmodule mod_a {\n    let decl_a_1 = ...\n    let decl_a_2 = (from mod_b.decl_b | take 10)\n}\nmodule mod_b {\n    let decl_b = (from mod_a.decl_a | take 10)\n}\n```\n\nReferences between variable declarations cannot be cyclic.\n\n```\nlet decl_a = (from decl_b)\nlet decl_b = (from decl_a) # error: cyclic reference\n```\n\n```\nmodule mod_a {\n    let decl_a = (from mod_b.decl_b)\n}\nmodule mod_b {\n    let decl_b = (from mod_a.decl_a) # error: cyclic reference\n}\n```\n\n## Compiler interface\n\n`prqlc` provides two interfaces for compiling files.\n\n**Multi-file interface** requires three arguments:\n\n- path to the file containing the module which is the root of the module\n  structure,\n- identifier of the pipeline that should be compiled to RQ (this can also be an\n  identifier of a module that has a `main` pipeline) and,\n- a \"file loader\", which can load files on-demand.\n\nThe path to the root module can be automatically detected by searching for\n`.prql` files starting with `_` in the current working directory.\n\nExample prqlc usage:\n\n```\n$ prqlc compile _project.prql sales.projections.orders_2024\n$ prqlc compile sales.projections.orders_2024\n```\n\n**Single-file interface** requires a single argument; the PRQL source. Any\nattempts to load modules in this mode result in compilation errors. This\ninterface is needed, for example, when integrating the compiler with a database\nconnector (i.e. JDBC) where no other files can be loaded.\n\n## Built-in module structure\n\n> As noted above, this facility is in discussion.\n\n```\n# root module of every project\nmodule project {\n\tmodule std {\n\t\tlet sum = a -> ...\n\t\tlet mean = a -> ...\n\t}\n\n\tmodule default_db {\n\t\t# all inferred tables and defined CTEs\n\t}\n\n\tlet main = (\n\t\tfrom t = tracks\n\t\tselect [track_id, title]\n\t)\n}\n```\n\n## Example\n\n<!-- prettier-ignore -->\n> [!WARNING]\n> The examples below do **not** work. At this time (Spring 2024), the\n> `module` facility does not work within a PRQL query itself. That is, a\n> `module` statement in a query cannot import files from the local file system.\n\nThis is an example project, where each of code block is a separate file.\n\n```\n# _project.prql\n\nmodule employees\nmodule sales\nmodule util\n```\n\n```\n# employees.prql\n\nlet employees = (...)\n\nlet salaries = (...)\n\nlet departments = (...)\n```\n\n```\n# sales/_sales.prql\n\nmodule orders\nmodule projections\n\nlet revenue_by_source = (...)\n```\n\n```\n# sales/orders.prql\n\nlet current_year = (...)\n\nlet archived = (...)\n\nlet by_employee = (from orders | join employees.employees ...)\n```\n\n```\n# sales/projections.prql\n\nlet orders_2023 = (from orders.current_year | append orders.archived)\n\nlet orders_2024 = (...)\n```\n\n```\n# util.prql\n\nlet pretty_print_num = col -> (...)\n```\n\n---\n\nSources:\n\n- [Notes On Module System](https://matklad.github.io/2021/11/27/notes-on-module-system.html),\n  by @matklad.\n"
  },
  {
    "path": "web/book/src/reference/spec/name-resolution.md",
    "content": "# Name resolution\n\nBecause PRQL primarily handles relational data, it has specialized scoping rules\nfor referencing columns.\n\n## Scopes\n\nIn PRQL's compiler, a scope is the collection of all names one can reference\nfrom a specific point in the program.\n\nIn PRQL, names in the scope are composed from namespace and variable name which\nare separated by a dot, similar to SQL. Namespaces can contain many dots, but\nvariable names cannot.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> For example, name `my_table.some_column` is a variable `some_column`\n> from namespace `my_table`.\n>\n> Name `foo.bar.baz` is a variable `baz` from namespace `foo.bar`.\n\nWhen processing a query, a scope is maintained and updated for each point in the\nquery.\n\nIt start with only namespace `std`, which is the standard library. It contains\ncommon functions like `sum` or `count`, along with all transform functions such\nas `derive` and `group`.\n\nIn pipelines (or rather in transform functions), scope is also injected with\nnamespaces of tables which may have been referenced with `from` or `join`\ntransforms. These namespaces contain simply all the columns of the table and\npossibly a wildcard variable, which matches any variable (see the algorithm\nbelow). Within transforms, there is also a special namespace that does not have\na name. It is called a _\"frame\"_ and it contains columns of the current table\nthe transform is operating on.\n\n## Resolving\n\nFor each ident we want to resolve, we search the scope's items in order. One of\nthree things can happen:\n\n- Scope contains an exact match, e.g. a name that matches in namespace and the\n  variable name.\n\n- Scope does not contain an exact match, but the ident did not specify a\n  namespace, so we can match a namespace that contains a `*` wildcard. If\n  there's a single namespace, the matched namespace is also updated to contain\n  this new variable name.\n\n- Otherwise, the nothing is matched and an error is raised.\n\n## Translating to SQL\n\nWhen translating into an SQL statement which references only one table, there is\nno need to reference column names with table prefix.\n\n```prql\nfrom employees\nselect first_name\n```\n\nBut when there are multiple tables and we don't have complete knowledge of all\ntable columns, a column without a prefix (i.e. `first_name`) may actually reside\nin multiple tables. Because of this, we have to use table prefixes for all\ncolumn names.\n\n```prql\nfrom employees\nderive {first_name, dept_id}\njoin d=departments (==dept_id)\nselect {first_name, d.title}\n```\n\nAs you can see, `employees.first_name` now needs table prefix, to prevent\nconflicts with potential column with the same name in `departments` table.\nSimilarly, `d.title` needs the table prefix.\n"
  },
  {
    "path": "web/book/src/reference/spec/null.md",
    "content": "# Null handling\n\nSQL has an unconventional way of handling `NULL` values, since it treats them as\nunknown values. As a result, in SQL:\n\n- `NULL` is not a value indicating a missing entry, but a placeholder for\n  anything possible,\n- `NULL = NULL` evaluates to `NULL`, since one cannot know if one unknown is\n  equal to another unknown,\n- `NULL <> NULL` evaluates to `NULL`, using same logic,\n- to check if a value is `NULL`, SQL introduces `IS NULL` and `IS NOT NULL`\n  operators,\n- `DISTINCT column` may return multiple `NULL` values.\n\nFor more information, check out the\n[Postgres documentation](https://www.postgresql.org/docs/current/functions-comparison.html).\n\nPRQL, on the other hand, treats `null` as a value, which means that:\n\n- `null == null` evaluates to `true`,\n- `null != null` evaluates to `false`,\n- distinct column cannot contain multiple `null` values.\n\n```prql\nfrom employees\nfilter first_name == null\nfilter null != last_name\n```\n\nNote that PRQL doesn't change how `NULL` is compared between columns, for\nexample in joins. (PRQL compiles to SQL and so can't change the behavior of the\ndatabase).\n\nFor more context or to provide feedback check out the discussion on\n[issue #99](https://github.com/PRQL/prql/issues/99).\n"
  },
  {
    "path": "web/book/src/reference/spec/type-system.md",
    "content": "# Type system\n\n> Status: under development\n\n> The type system determines the allowed values of a term.\n\n## Purpose\n\nEach of the SQL DBMSs has their own type system. Thanks to the SQL standard,\nthey are very similar, but have key differences regardless. For example, SQLite\ndoes not have a type for date or time or timestamps, but it has functions for\nhandling date and time that take ISO 8601 strings or integers that represent\nUnix timestamps. So it does support most of what is possible to do with dates in\nother dialects, even though it stores data with a different physical layout and\nuses different functions to achieve that.\n\nPRQL's task is to define it's own description of _data formats_, just as how it\nalready defines common _data transformations_.\n\nThis is done in two steps:\n\n1. Define PRQL's Type System (PTS), following principles we think a relational\n   language should have (and not fixate on what existing SQL DBMSs have).\n\n2. Define a mapping between SQL Type System (STS) and PTS, for each of the\n   DBMSs. Ideally we'd want that to be a bijection, so each type in PTS would be\n   represented by a single type in STS and vice-versa. Unfortunately this is not\n   entirely possible, as shown below.\n\nIn practical terms, we want for a user to be able to:\n\n- ... express types of their database with PRQL (map their STS into PTS). In\n  some cases, we can allow to say \"your database is not representable with PRQL,\n  change it or use only a subset of it\". An example of what we don't want to\n  support are arrays with arbitrary indexes in Postgres (i.e. 2-based index for\n  arrays).\n\n  This task of mapping to PTS could be automated by LSP server, by introspecting\n  user's SQL database and generating PRQL source.\n\n- ... express their SQL queries in PRQL. Again, using mapping from STS to PTS,\n  one should be able to express any SQL operation in PRQL.\n\n  For example, translate MSSQL `DATEDIFF` to subtraction operator `-` in PRQL.\n\n  For now, this mapping is manual, but should be documented and may be\n  automated.\n\n- ... use any PRQL feature in their database. Here we are mapping from PTS into\n  an arbitrary STS.\n\n  For example, translate PRQL's datetime operations to use TEXT in SQLite.\n\n  As of now, prqlc already does a good job of automatically doing this mapping.\n\nExample of the mapping between PTS and two STSs:\n\n| PTS       | STS Postgres | STS SQLite |\n| --------- | ------------ | ---------- |\n| int32     | integer      | INTEGER    |\n| int64     | bigint       | INTEGER    |\n| timestamp | timestamp    | TEXT       |\n\n## Principles\n\n**Algebraic types** - have a way of expressing sum and product types. In Rust,\nsum would be an enum and product would be tuple or a struct. In SQL, product\nwould be a row, since it can contain different types, all at once. Sum would be\nharder to express, see\n[this post](https://www.parsonsmatt.org/2019/03/19/sum_types_in_sql.html).\n\nThe value proposition here is that algebraic types give a lot modeling\nflexibility, all while being conceptually simple.\n\n**Composable** - as with transformation, we'd want types to compose together.\n\nUsing Python, JavaScript, C++ or Rust, one could define many different data\nstructures that would correspond to our idea of \"relation\". Most of them would\nbe an object/struct that has column names and types and then a generic array of\narrays for rows.\n\nPRQL's type system should also be able to express relations as composed from\nprimitive types, but have only one idiomatic way of doing so.\n\nIn practice, this means that builtin types include only primitives (int, text,\nbool, float), tuple (for product), enum (for sum) and array (for repeating). An\nSQL row translates to a tuple, and a relation translates to an array of tuples.\n\nComposability also leads to a minimal type system, which does not differentiate\nbetween tuples, objects and structs. A single product type is enough.\n\n**No subtyping** - avoid super types and inheritance.\n\nSubtyping is a natural extension to a type system, where a type can be a super\ntype of some other type. This is base mechanism for Object Oriented Programming,\nbut is also present in most dynamically types languages. For example, a type\n`number` might be super type of `int` and `float`.\n\nPTS does not have subtyping, because it requires dynamic dispatch and because it\nadds unnecessary complexity to generic type arguments.\n\nDynamic dispatch, is a mechanism that would be able, for example, to call\nappropriate `to_string` function for each element of an array of `number`. This\narray contains both elements of type `int` and type `float`, with different\n`to_string` implementations.\n\n<!--\n> This segment was part of initial type system proposal.\n> I still do believe it would be useful and possible to implement,\n> but it should be updated to latest TS changes.\n\n**Type constraints** - constrain a type with a predicate. For example, have a\ntype of `int64`s that are equal or greater than 10. Postgres\n[does support this](https://news.ycombinator.com/item?id=34835063). The primary\nvalue of using constrained types would not be validation (as it is used in\nlinked article), but when matching the type.\n\nSay, for example, that we have a pipeline like this:\n\n```\nderive color = switch [x => 'red', true => 'green']\nderive is_red = switch [color == 'red' => true, color == 'green' => false]\n```\n\nIt should be possible to infer that `color` is of type `text`, but only when\nequal to `'red'` or `'green'`. This means that the second switch covers all\npossible cases and `is_red` cannot be `null`.\n-->\n\n## Definition\n\n> For any undefined terms used in this section, refer to set theory and\n> mathematical definitions in general.\n\nA \"type of a variable\" is a \"set of all possible values of that variable\".\n\n### Primitives\n\nAt the moment of writing, PRQL defines following primitive types: `int`,\n`float`, `bool`, `text`, `date`, `time` and `timestamp`. New primitive types\nwill be added in the future and some of existing types might be split into\nsmaller subsets (see section \"Splitting primitives\").\n\n### Tuples\n\nTuple type is a product type.\n\nIt contains n ordered fields, where n is known at compile-time. Each field has a\ntype itself and an optional name. Fields are not necessarily of the same type.\n\nIn other languages, similar constructs are named record, struct, tuple, named\ntuple or (data)class.\n\n```\ntype my_row = {id = int, bool, name = str}\n```\n\n### Arrays\n\nArray is a container type that contains n ordered fields, where n is not known\nat compile-time. All fields are of the same type and cannot be named.\n\n```\ntype array_of_int = [int]\n```\n\n### Functions\n\n```\ntype floor_signature = func float -> int\n```\n\n### Union\n\n```\ntype status = (\n  paid = () ||\n  unpaid = float ||\n  {reason = text, cancelled_at = timestamp} ||\n)\n```\n\nThis is \"a sum type\".\n\n## Type annotations\n\nVariable annotations and function parameters may specify type annotations:\n\n```\nlet a <t> = x\n```\n\nThe value of `x` (and thus `a`) must be an element of `t`.\n\n```\nlet my_func = func x <t> -> y\n```\n\nThe value of argument supplied to `x` must be an element of `t`.\n\n```\nlet my_func = func x -> <t> y\n```\n\nThe value of function body `y` must be an element of `t`.\n\n## Physical layout\n\n_Logical type_ is user-facing the notion of a type that is the building block of\nthe type system.\n\n_Physical layout_ is the underlying memory layout of the data represented by a\nvariable.\n\nIn many programming languages, physical layout of a logical type is dependent on\nthe target platform. Similarly, physical layout of a PRQL logical type is\ndependent on representation of that type in the target STS.\n\n```\nPTS logical type  --->  STS logical type  ---> STS physical layout\n```\n\nNote that not all STS types do not have a single physical layout. Postgres has a\nlogical (pseudo)type `anyelement`, which is a super type of any data type. It\ncan be used as a function parameter type, but does not have a single physical\nlayout so it cannot be used in a column declaration.\n\nFor now, PRQL does not define physical layouts of any type. It is not needed\nsince PRQL is not used for DDL (see section \"Splitting primitives\") or does not\nsupport raw access to underlying memory.\n\nAs a consequence, results of a PRQL query cannot be robustly compared across\nDBMSs, since the physical layout of the result will vary.\n\nIn the future, PRQL may define a common physical layout of types, probably using\nApache Arrow.\n\n## Examples\n\n```\ntype my_relation = [{\n\tid = int,\n\ttitle = text,\n\tage = int\n}]\n\ntype invoices = [{\n    invoice_id = int64,\n    issued_at = timestamp,\n    labels = [text]\n\n    #[repr(json)]\n    items = [{\n        article_id = int64,\n        count = int16 where x -> x >= 1,\n    }],\n    paid_by_user_id = (int64 || null),\n}]\n```\n\n## Appendix\n\n### Splitting primitives\n\nThis document mentions `int32` and `int64` as distinct types, but there is no\nneed for that in the initial implementation. The built-in `int` can associate\nwith all operations on integers and translate PRQL to valid SQL regardless of\nthe size of the integer. Later, `int` cam be replaced by `int8`, `int16`,\n`int32`, `int64`.\n\nThe general rule for \"when to make a distinction between types\" would be \"as\nsoon as the types carry different information and we find an operation that\nwould be expressed differently\". In this example, that would require some\noperation on `int32` to have different syntax than same operation over `int64`.\n\nWe can have such relaxed rule because PRQL is not aiming to be a Data Definition\nLanguage and does not have to bother with exact physical layout of types.\n\n### Type representations\n\nThere are cases where a PTS type has multiple possible and valid representations\nin some STSs.\n\nFor such cases, we'd want to support the use of alternative representations for\nstoring data, but also application of any function that is defined for the\noriginal type.\n\nUsing SQLite as an example again, users may have some temporal data stored as\nINTEGER unix timestamp and some as TEXT that contains ISO 8601 without timezone.\nFrom the user's perspective, both of these types are `timestamp`s and should be\ndeclared as such. But when compiling operations over these types to SQL, the\ncompiler should consider their different representations in STS. For example a\ndifference between two timestamps `timestamp - timestamp` can be translated to a\nnormal int subtraction for INTEGER repr, but must apply SQLite's function\n`unixepoch` when dealing with TEXT repr.\n\nTable declarations should therefore support annotations that give hints about\nwhich representation is used:\n\n```\ntable foo {\n    #[repr(text)]\n    created_at: timestamp,\n}\n```\n\nA similar example is an \"array of strings type\" in PTS that could be represented\nby a `text[]` (if DBMS supports arrays) or `json` or it's variant `jsonb` in\nPostgres. Again, the representation would affect operators: in Postgres, arrays\nwould be accessed with `my_array[1]` and json arrays would use\n`my_json_array -> 1`. This example may not be applicable, if we decide that we\nwant a separate JSON type in PST.\n\n### RQ functions, targets and reprs\n\n> This part is talks about technical implementations, not the language itself\n\n#### Idea\n\nRQ contains a single node kind for expressing operations and functions:\nBuiltInFunction (may be renamed in the future).\n\nIt is a bottleneck that we can leverage when trying to affect how an operator or\na function interacts with different type representations on different targets.\n\nIdea is to implement the BuiltInFunction multiple times and annotate it with it\nintended target and parameter representation. Then we can teach the compiler to\npick the appropriate function implementation that suit current repr and\ncompilation target.\n\n#### Specifics\n\nRQ specification is an interface that contains functions, identified by name\n(i.e. `std.int8.add`). These functions have typed parameters and a return value.\nIf an RQ function call does not match the function declaration in number or in\ntypes of the parameters, this is considered an invalid RQ AST.\n\nWe provide multiple implementations for each RQ function. They are annotated\nwith a target (i.e. `#[target(sql.sqlite)]`) and have their params annotated\nwith type reprs (i.e. `#[repr(int)]`).\n\n```\n# using a made-up syntax\n\n#[target(sql.sqlite)]\nfunc std.int8.add\n    #[repr(int8)] x\n    #[repr(int8)] y\n    -> s\"{x} + {y}\"\n```\n\nEach RQ type has one canonical repr that serves as the reference implementation\nfor other reprs and indicates the amount of contained data (i.e. 1 bit, 8 bits,\n64 bits).\n\n#### Example\n\nLet's say for example, that we'd want to support 8bit integer arithmetic, and\nthat we'd want the result of `127 + 1` to be `-128` (ideally we'd handle this\nbetter, but bear with me for the sake of the example). Because some RDBMSs don't\nsupport 8bit numbers and do all their integer computation with 64bit numbers\n(SQLite), we need to implement an alternative type representation for that\ntarget.\n\nThe logical type `int8` could have the following two reprs:\n\n- canonical `repr_int8` that contains 8 bits in two's complement, covering\n  integer values in range -128 to 127 (inclusive),\n- `repr_int64` that contains 64 bits of data, but is using only the values that\n  are also covered by `repr_int8`.\n\nNow we'd implement function `std.int8.add` for each of the reprs. Let's assume\nthat the `int8` implementation is straightforward and that databases don't just\nchange the data type when a number overflows. The impl for `int64` requires a\nCASE statement that checks if the value would overflow and subtact 256 in that\ncase.\n\nThe goal here is that the results of the two impls are equivalent. To validate\nthat, we also need a way to convert between the reprs, or another `to_string`\nfunction, implemented for both reprs.\n"
  },
  {
    "path": "web/book/src/reference/stdlib/README.md",
    "content": "# Standard library\n\nThe standard library currently contains commonly used functions that are used in\nSQL. It's not yet as broad as we'd like, and we're very open to expanding it.\n\nCurrently s-strings are an escape-hatch for any function that isn't in our\nstandard library. If we find ourselves using them for something frequently,\nraise an issue and we'll add it to the stdlib.\n\nHere's the source of the current\n[PRQL `std`](https://github.com/PRQL/prql/blob/main/prqlc/prqlc/src/semantic/std.prql):\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> PRQL 0.9.0 has started supporting different DB implementations for\n> standard library functions. The source is the\n> [`std.sql`](https://github.com/PRQL/prql/blob/main/prqlc/prqlc/src/sql/std.sql.prql).\n\n```prql no-eval\n{{#include ../../../../../prqlc/prqlc/src/semantic/std.prql}}\n```\n\nAnd a couple of examples:\n\n```prql\nfrom employees\nderive {\n  gross_salary = (salary + payroll_tax | as int),\n  gross_salary_rounded = (gross_salary | math.round 0),\n  time = s\"NOW()\",  # an s-string, given no `now` function exists in PRQL\n}\n```\n\nExample of different implementations of division and integer division:\n\n```prql\nprql target:sql.sqlite\n\nfrom [{x = 13, y = 5}]\nselect {\n  quotient = x / y,\n  int_quotient = x // y,\n}\n```\n\n```prql\nprql target:sql.mysql\n\nfrom [{x = 13, y = 5}]\nselect {\n  quotient = x / y,\n  int_quotient = x // y,\n}\n```\n"
  },
  {
    "path": "web/book/src/reference/stdlib/date.md",
    "content": "# Date functions\n\nThese are all the functions defined in the `date` module:\n\n### `to_text`\n\nConverts a date into a text.\\\nSince there are many possible date representations, `to_text` takes a `format`\nparameter that describes thanks to [specifiers](#date--time-format-specifiers)\nhow the date or timestamp should be structured.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Since all RDBMS have different ways to format dates and times, PRQL\n> **requires an explicit dialect** to be specified\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> For now the supported DBs are: BigQuery, Clickhouse, DuckDB, MySQL, MSSQL\n> and Postgres.\n\n```prql\nprql target:sql.duckdb\n\nfrom invoices\nselect (invoice_date | date.to_text \"%d/%m/%Y\")\n\n```\n\n```prql\nprql target:sql.postgres\n\nfrom invoices\nselect (invoice_date | date.to_text \"%d/%m/%Y\")\n\n```\n\n```prql\nprql target:sql.mysql\n\nfrom invoices\nselect (invoice_date | date.to_text \"%d/%m/%Y\")\n\n```\n\n### Date & time format specifiers\n\nPRQL specifiers for date and time formatting is a subset of specifiers used by\n[`chrono`](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).\n\nHere is the list of the specifiers currently supported:\n\n| Spec. | Example                       | Description                                                      |\n| ----- | ----------------------------- | ---------------------------------------------------------------- |\n|       |                               |                                                                  |\n|       |                               | **DATE SPECIFIERS:**                                             |\n| `%Y`  | `2001`                        | Year number, zero-padded to 4 digits                             |\n| `%y`  | `01`                          | Year number, zero-padded to 2 digits                             |\n| `%m`  | `07`                          | Month number (01–12), zero-padded to 2 digits                    |\n| `%-m` | `7`                           | Month number (1-12)                                              |\n| `%b`  | `Jul`                         | Abbreviated month name. Always 3 letters.                        |\n| `%B`  | `July`                        | Full month name                                                  |\n| `%d`  | `08`                          | Day number (01-31), zero-padded to 2 digits                      |\n| `%-d` | ` 8`                          | Day number (1-31)                                                |\n| `%a`  | `Sun`                         | Abbreviated weekday name. Always 3 letters                       |\n| `%A`  | `Sunday`                      | Full weekday name                                                |\n| `%D`  | `07/08/01`                    | Month-day-year format. Same as `%m/%d/%y`                        |\n| `%x`  | `07/08/01`                    | Locale's date representation                                     |\n| `%F`  | `2001-07-08`                  | Year-month-day format (ISO 8601). Same as `%Y-%m-%d`             |\n|       |                               |                                                                  |\n|       |                               | **TIME SPECIFIERS:**                                             |\n| `%H`  | `00`                          | Hour number (00-23)                                              |\n| `%k`  | ` 0`                          | Same as `%H` but space-padded. Same as `%_H`.                    |\n| `%I`  | `12`                          | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits. |\n| `%p`  | `AM`                          | `AM` or `PM` in 12-hour clocks.                                  |\n| `%M`  | `34`                          | Minute number (00-59), zero-padded to 2 digits.                  |\n| `%S`  | `60`                          | Second number (00-59), zero-padded to 2 digits.                  |\n| `%f`  | `264900`                      | Number of microseconds[^1] since last whole second               |\n| `%R`  | `00:34`                       | Hour-minute format. Same as `%H:%M`.                             |\n| `%T`  | `00:34:60`                    | Hour-minute-second format. Same as `%H:%M:%S`.                   |\n| `%X`  | `00:34:60`                    | Locale's time representation (e.g., 23:13:48).                   |\n| `%r`  | `12:34:60 AM`                 | Locale's 12 hour clock time. (e.g., 11:11:04 PM)                 |\n|       |                               |                                                                  |\n|       |                               | **DATE & TIME SPECIFIERS:**                                      |\n| `%+`  | `2001-07-08T00:34:60.026490Z` | ISO 8601 / RFC 3339 date & time format.                          |\n|       |                               |                                                                  |\n|       |                               | **SPECIAL SPECIFIERS:**                                          |\n| `%t`  |                               | Literal tab (`\\t`).                                              |\n| `%n`  |                               | Literal newline (`\\n`).                                          |\n| `%%`  |                               | Literal percent sign.                                            |\n\n[^1]: This is different from chrono, for which `%f` represents nanoseconds\n"
  },
  {
    "path": "web/book/src/reference/stdlib/distinct.md",
    "content": "# How do I: remove duplicates?\n\nPRQL doesn't have a specific `distinct` keyword. Instead duplicate tuples in a\nrelation can be removed by using `group` and `take 1`:\n\n```prql\nfrom employees\nselect department\ngroup employees.* (\n  take 1\n)\n```\n\nThis also works with a wildcard:\n\n```prql\nfrom employees\ngroup employees.* (take 1)\n```\n\n## Remove duplicates from each group?\n\nTo\n[select a single row from each group](https://stackoverflow.com/questions/3800551/select-first-row-in-each-group-by-group)\n`group` can be combined with `sort` and `take`:\n\n```prql\n# youngest employee from each department\nfrom employees\ngroup department (\n  sort age\n  take 1\n)\n```\n\nNote that we can't always compile to `DISTINCT`; when the columns in the `group`\naren't all the available columns, we need to use a window function:\n\n```prql\nfrom employees\ngroup {first_name, last_name} (take 1)\n```\n\n<!-- TODO: uncomment when the bug is fixed -->\n\n<!-- When compiling to Postgres or DuckDB dialect, such queries will be compiled to\n`DISTINCT ON`, which is\n[the most performant option](https://stackoverflow.com/a/7630564).\n\n```prql\nprql target:sql.postgres\n\nfrom employees\ngroup department (\n  sort age\n  take 1\n)\n``` -->\n"
  },
  {
    "path": "web/book/src/reference/stdlib/math.md",
    "content": "# Mathematical functions\n\nThese are all the functions defined in the `math` module:\n\n| function | parameters | description                        |\n| -------- | ---------- | ---------------------------------- |\n| abs      | `col`      | Absolute value of `col`            |\n| acos     | `col`      | Arccosine of `col`                 |\n| asin     | `col`      | Arcsine of `col`                   |\n| atan     | `col`      | Arctangent of `col`                |\n| ceil     | `col`      | Rounds the number up of `col`      |\n| cos      | `col`      | Cosine of `col`                    |\n| degrees  | `col`      | Converts radians to degrees        |\n| exp      | `col`      | Exponential of `col`               |\n| floor    | `col`      | Rounds the number down             |\n| ln       | `col`      | Natural logarithm of `col`         |\n| log      | `b` `col`  | `b`-log of `col`                   |\n| log10    | `col`      | 10-log of `col`                    |\n| pi       |            | The constant π                     |\n| pow      | `b` `col`  | Computes `col` to the power `b`    |\n| radians  | `col`      | Converts degrees to radians        |\n| round    | `n` `col`  | Rounds `col` to `n` decimal places |\n| sin      | `col`      | Sin of `col`                       |\n| sqrt     | `col`      | Square root of `col`               |\n| tan      | `col`      | Tangent of `col`                   |\n\n## Example\n\n```prql\nfrom employees\nselect age_squared = (age | math.pow 2)\n```\n"
  },
  {
    "path": "web/book/src/reference/stdlib/text.md",
    "content": "# Text functions\n\nThese are all the functions defined in the `text` module:\n\n| function    | parameters             | description                                                                   |\n| ----------- | ---------------------- | ----------------------------------------------------------------------------- |\n| contains    | `sub` `col`            | Returns true if `col` contains `sub`                                          |\n| ends_with   | `sub` `col`            | Returns true if `col` ends with `sub`                                         |\n| extract     | `idx` `len` `col`      | Extracts a substring at the index `idx` (starting at 1) with the length `len` |\n| length      | `col`                  | Returns the number of characters in `col`                                     |\n| lower       | `col`                  | Converts `col` to lower case                                                  |\n| ltrim       | `col`                  | Removes all the whitespaces from the left side of `col`                       |\n| replace     | `before` `after` `col` | Replaces any occurrences of `before` with `after` in `col`                    |\n| rtrim       | `col`                  | Removes all the whitespaces from the right side of `col`                      |\n| starts_with | `sub` `col`            | Returns true if `col` starts with `sub`                                       |\n| trim        | `col`                  | Removes all the whitespaces from both sides of `col`                          |\n| upper       | `col`                  | Converts `col` to upper case                                                  |\n\n## Example\n\n```prql\nfrom employees\nselect {\n  (last_name | text.lower | text.starts_with(\"a\")),\n  (title | text.replace \"manager\" \"chief\"),\n}\n```\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/README.md",
    "content": "# Transforms\n\nTransforms are functions that take a relation and produce a relation.\n\nUsually they are chained together into a pipeline, which resembles an SQL query.\n\nTransforms were designed with a focus on modularity, so each of them is\nfulfilling a specific purpose and has defined invariants (properties of the\nrelation that are left unaffected). That's often referred to as \"orthogonality\"\nand its goal is to keep transform functions composable by minimizing\ninterference of their effects. Additionally, it also keeps the number of\ntransforms low.\n\nFor example, `select` and `derive` will not change the number of rows, while\n`filter` and `take` will not change the number of columns.\n\nIn SQL, we can see this lack of invariant when an aggregation function is used\nin the `SELECT` clause. Before, the number of rows was kept constant, but\nintroduction of an aggregation function caused the whole statement to produce\nonly one row (per group).\n\nThese are the currently available transforms:\n\n| Transform   | Purpose                                                                         | SQL Equivalent              |\n| ----------- | ------------------------------------------------------------------------------- | --------------------------- |\n| `derive`    | [Compute new columns](./derive.md)                                              | `SELECT *, ... AS ...`      |\n| `select`    | [Pick & compute columns](./select.md)                                           | `SELECT ... AS ...`         |\n| `filter`    | [Pick rows based on their values](./filter.md)                                  | `WHERE`, `HAVING`,`QUALIFY` |\n| `sort`      | [Order rows based on the values of columns](./sort.md)                          | `ORDER BY`                  |\n| `join`      | [Add columns from another table, matching rows based on a condition](./join.md) | `JOIN`                      |\n| `take`      | [Pick rows based on their position](./take.md)                                  | `TOP`, `LIMIT`, `OFFSET`    |\n| `group`     | [Partition rows into groups and applies a pipeline to each of them](./group.md) | `GROUP BY`, `PARTITION BY`  |\n| `aggregate` | [Summarize many rows into one row](./aggregate.md)                              | `SELECT foo(...)`           |\n| `window`    | [Apply a pipeline to overlapping segments of rows](./window.md)                 | `OVER`, `ROWS`, `RANGE`     |\n| `loop`      | [Iteratively apply a function to a relation until it's empty](./loop.md)        | `WITH RECURSIVE ...`        |\n\n## See also\n\n- [`from`](../../data/from.md) — `from` is the main way of getting data into a\n  pipeline (it's not listed above since it's not technically a transform, since\n  it doesn't receive an input).\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/aggregate.md",
    "content": "# Aggregate\n\nSummarizes many rows into one row.\n\nWhen applied:\n\n- without `group`, it produces one row from the whole table,\n- within a `group` pipeline, it produces one row from each group.\n\n```prql no-eval\naggregate {expression or assign operations}\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Currently, all declared aggregation functions are `min`, `max`,\n> `count`, `average`, `stddev`, `avg`, `sum` and `count_distinct`. We are in the\n> process of filling out [std lib](../).\n\n## Examples\n\n```prql\nfrom employees\naggregate {\n  average salary,\n  ct = count salary\n}\n```\n\n```prql\nfrom employees\ngroup {title, country} (\n  aggregate {\n    average salary,\n    ct = count salary,\n  }\n)\n```\n\n## Aggregate is required\n\nUnlike in SQL, using an aggregation function in `derive` or `select` (or any\nother transform except `aggregate`) will not trigger aggregation. By default,\nPRQL will interpret such attempts functions as window functions:\n\n```prql\nfrom employees\nderive {avg_sal = average salary}\n```\n\nThis ensures that `derive` does not manipulate the number of rows, but only ever\nadds a column. For more information, see [window transform](./window.md).\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/append.md",
    "content": "# Append\n\nConcatenates two tables together.\n\nEquivalent to `UNION ALL` in SQL. The number of rows is always the sum of the\nnumber of rows from the two input tables. To replicate `UNION DISTINCT`, see\n[set operations](#set-operations).\n\n```prql\nfrom employees_1\nappend employees_2\n```\n\n## Remove\n\n> _experimental_\n\nRemoves rows that appear in another relation, like `EXCEPT ALL`. Duplicate rows\nare removed one-for-one.\n\n```prql\nfrom employees_1\nremove employees_2\n```\n\n## Intersection\n\n> _experimental_\n\n```prql\nfrom employees_1\nintersect employees_2\n```\n\n## Set operations\n\n> _experimental_\n\nTo imitate set operations i.e. (`UNION`, `EXCEPT` and `INTERSECT`), you can use\nthe following functions:\n\n```prql no-eval\nlet distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\nlet union = `default_db.bottom` top -> (top | append bottom | distinct)\nlet except = `default_db.bottom` top -> (top | distinct | remove bottom)\nlet intersect_distinct = `default_db.bottom` top -> (top | intersect bottom | distinct)\n```\n\nDon't mind the `default_db.` and `noop`, these are compiler implementation\ndetail for now.\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/derive.md",
    "content": "# Derive\n\nComputes one or more new columns.\n\n```prql no-eval\nderive {\n  name = expression,\n  # or\n  column,\n}\n```\n\n## Examples\n\n```prql\nfrom employees\nderive gross_salary = salary + payroll_tax\n```\n\n```prql\nfrom employees\nderive {\n  gross_salary = salary + payroll_tax,\n  gross_cost = gross_salary + benefits_cost\n}\n```\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/filter.md",
    "content": "# Filter\n\nPicks rows based on their values.\n\n```prql no-eval\nfilter boolean_expression\n```\n\n## Examples\n\n```prql\nfrom employees\nfilter age > 25\n```\n\n```prql\nfrom employees\nfilter (age > 25 || department != \"IT\")\n```\n\n```prql\nfrom employees\nfilter (department | in [\"IT\", \"HR\"])\n```\n\n```prql\nfrom employees\nfilter (age | in 25..40)\n```\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/group.md",
    "content": "# Group\n\nPartitions the rows into groups and applies a pipeline to each of the groups.\n\n```prql no-eval\ngroup {key_columns} (pipeline)\n```\n\nThe partitioning of groups are determined by the `key_column`s (first argument).\n\nThe most conventional use of `group` is with `aggregate`:\n\n```prql\nfrom employees\ngroup {title, country} (\n  aggregate {\n    average salary,\n    ct = count salary\n  }\n)\n```\n\nIn concept, a transform in context of a `group` does the same transformation to\nthe group as it would to the table — for example finding the employee who joined\nfirst across the whole table:\n\n```prql\nfrom employees\nsort join_date\ntake 1\n```\n\nTo find the employee who joined first in each department, it's exactly the same\npipeline, but within a `group` expression:\n\n```prql\nfrom employees\ngroup role (\n  sort join_date  # taken from above\n  take 1\n)\n```\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/join.md",
    "content": "# Join\n\nAdds columns from another relation, matching rows based on a condition.\n\n```prql no-eval\njoin side:{inner|left|right|full} rel (condition)\n```\n\n## Parameters\n\n- `side` specifies which rows to include, defaulting to `inner`.\n- `rel` - the relation to join with, possibly including an alias, e.g.\n  `a=artists`.\n- `condition` - the criteria on which to match the rows from the two relations.\n  Theoretically, `join` will produce a cartesian product of the two input\n  relations and then filter the result by the condition. It supports two\n  additional features:\n  - _Names [`this` & `that`](../../syntax/keywords.md#this--that)_: Along name\n    `this`, which refers to the first input relation, `condition` can use name\n    `that`, which refers to the second input relation.\n  - _Self equality operator_: If the condition is an equality comparison between\n    two columns with the same name (i.e. `(this.col == that.col)`), it can be\n    expressed with only `(==col)`.\n\n## Examples\n\n```prql\nfrom employees\njoin side:left positions (employees.id==positions.employee_id)\n```\n\n---\n\n```prql\nfrom employees\njoin side:left p=positions (employees.id==p.employee_id)\n```\n\n---\n\n```prql\nfrom tracks\njoin side:left artists (\n  # This adds a `country` condition, as an alternative to filtering\n  artists.id==tracks.artist_id && artists.country=='UK'\n)\n```\n\n---\n\nIn SQL, CROSS JOIN is a join that returns each row from first relation matched\nwith all rows from the second relation. To accomplish this, we can use condition\n`true`, which will return all rows of the cartesian product of the input\nrelations:\n\n```\nfrom shirts\njoin hats true\n```\n\n---\n\n[`this` & `that`](../../syntax/keywords.md#this--that) can be used to refer to\nthe current & other table respectively:\n\n```prql\nfrom tracks\njoin side:inner artists (\n  this.id==that.artist_id\n)\n```\n\n---\n\nIf the join conditions are of form `left.x == right.x`, we can use \"self\nequality operator\":\n\n```prql\nfrom employees\njoin positions (==emp_no)\n```\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/loop.md",
    "content": "# Loop\n\n> _Experimental_\n\n```prql no-eval\nloop {step_function} {initial_relation}\n```\n\nIteratively applies `step` function to `initial` relation until the `step`\nreturns an empty table. Returns a relation that contains rows of initial\nrelation and all intermediate relations.\n\nThis behavior could be expressed with following pseudo-code:\n\n```python\ndef loop(step, initial):\n    result = []\n    current = initial\n    while current is not empty:\n        result = append(result, current)\n        current = step(current)\n\n    return result\n```\n\n## Examples\n\n```prql\nfrom [{n = 1}]\nloop (\n    filter n<4\n    select n = n+1\n)\n\n# returns [1, 2, 3, 4]\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> The behavior of `WITH RECURSIVE` may depend on the database\n> configuration in MySQL. The compiler assumes the behavior described by the\n> [Postgres documentation](https://www.postgresql.org/docs/15/queries-with.html#QUERIES-WITH-RECURSIVE)\n> and will not produce correct results for\n> [alternative configurations of MySQL](https://dev.mysql.com/doc/refman/8.0/en/with.html#common-table-expressions-recursive).\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Currently, `loop` may produce references to the recursive CTE in\n> sub-queries, which is not supported by some database engines, e.g. SQLite. For\n> now, we suggest step functions are kept simple enough to fit into a single\n> SELECT statement.\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/select.md",
    "content": "# Select\n\nPicks and computes columns.\n\n```prql no-eval\nselect {\n  name = expression,\n  # or\n  column,\n}\n# or\nselect !{column}\n```\n\n## Examples\n\n```prql\nfrom employees\nselect name = f\"{first_name} {last_name}\"\n```\n\n```prql\nfrom employees\nselect {\n  name = f\"{first_name} {last_name}\",\n  age_eoy = dob - @2022-12-31,\n}\n```\n\n```prql\nfrom employees\nselect first_name\n```\n\n```prql\nfrom e=employees\nselect {e.first_name, e.last_name}\n```\n\n### Excluding columns\n\nWe can use `!` to exclude a list of columns. This can operate in two ways:\n\n- We use `SELECT * EXCLUDE` / `SELECT * EXCEPT` for the columns supplied to\n  `select !{}` in dialects which support it.\n- Otherwise, the columns must have been defined prior in the query (unless all\n  of a table's columns are excluded); for example in another `select` or a\n  `group` transform. In this case, we evaluate and specify the columns that\n  should be included in the output SQL.\n\nSome examples:\n\n```prql\nprql target:sql.bigquery\nfrom tracks\nselect !{milliseconds, bytes}\n```\n\n```prql\nfrom tracks\nselect {track_id, title, composer, bytes}\nselect !{title, composer}\n```\n\n```prql\nfrom artists\nderive nick = name\nselect !{artists.*}\n```\n\nNote that `!` is also the `NOT` operator, so without the tuple it has a\ndifferent meaning:\n\n```prql\nprql target:sql.bigquery\nfrom tracks\nselect !is_compilation\n```\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/sort.md",
    "content": "# Sort\n\nOrder rows based on the values of one or more expressions (generally columns).\n\n```prql no-eval\nsort {(+|-) column}\n```\n\n## Parameters\n\n- One expression or a tuple of expressions to sort by\n- Each expression can be prefixed with:\n  - `+`, for ascending order, the default\n  - `-`, for descending order\n- When using prefixes, even a single expression needs to be in a tuple or\n  parentheses. (Otherwise, `sort -foo` is parsed as a subtraction between `sort`\n  and `foo`.)\n\n## Examples\n\n```prql\nfrom employees\nsort age\n```\n\n```prql\nfrom employees\nsort {-age}\n```\n\n```prql\nfrom employees\nsort {age, -tenure, +salary}\n```\n\nWe can also use expressions:\n\n```prql\nfrom employees\nsort {s\"substr({first_name}, 2, 5)\"}\n```\n\n## Ordering guarantees\n\nOrdering is persistent through a pipeline in PRQL. For example:\n\n```prql\nfrom employees\nsort tenure\njoin locations (==employee_id)\n```\n\nHere, PRQL pushes the `sort` down the pipeline, compiling the `ORDER BY` to the\n_end_ of the query. Consequently, most relation transforms retain the row order.\n\nThe explicit semantics are:\n\n- `sort` introduces a new order,\n- `group` resets the order,\n- `join` retains the order of the left relation,\n- database tables don't have a known order.\n\nComparatively, in SQL, relations possess no order, being orderable solely within\nthe context of the query result, `LIMIT` statement, or window function. The lack\nof inherent order can result in an unexpected reshuffling of a previously\nordered relation from a `JOIN` or windowing operation.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> To be precise — in PRQL, a relation is an _array of tuples_ and not a\n> set or a bag. The persistent nature of this order remains intact through\n> sub-queries and intermediate table definitions.\n\nFor instance, an SQL query such as:\n\n```sql\nWITH albums_sorted AS (\n  SELECT *\n  FROM albums\n  ORDER BY title\n)\nSELECT *\nFROM albums_sorted\nJOIN artists USING (artist_id)\n```\n\n...doesn't guarantee any row order (indeed — even without the `JOIN`, the SQL\nstandard doesn't guarantee an order, although most implementations will respect\nit).\n\n<!-- We rolling this back. Waiting on the outcome of https://github.com/PRQL/prql/issues/2622 -->\n\n<!-- ## Nulls\n\nPRQL defaults to `NULLS LAST` when compiling to SQL. Because databases have\ndifferent defaults, the compiler emits this for all targets for which it's not a\ndefault{{footnote: except for MSSQL, which doesn't support this}}.\n\nThe main benefit of this approach is that `take 42` will select non-null values\nfor both ascending and descending sorts, which is generally what is wanted.\n\nThere isn't currently a way to change this for a query, but if that would be\nhelpful, please raise an issue.\n\nNote how DuckDB doesn't require a `NULLS LAST`, unlike the generic targets\nabove:\n\n```prql\nprql target:sql.duckdb\n\nfrom artists\nsort artist_id\ntake 42\n```\n\n```admonish info\nCheck out [DuckDB #7174](https://github.com/duckdb/duckdb/pull/7174) for a survey of various databases' implementations.\n``` -->\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/take.md",
    "content": "# Take\n\nPicks rows based on their position.\n\n```prql no-eval\ntake (n|range)\n```\n\nSee [Ranges](../../syntax/ranges.md) for more details on how ranges work.\n\n## Examples\n\n```prql\nfrom employees\ntake 10\n```\n\n```prql\nfrom orders\nsort {-value, created_at}\ntake 101..110\n```\n"
  },
  {
    "path": "web/book/src/reference/stdlib/transforms/window.md",
    "content": "# Window\n\nApplies a pipeline to segments of rows, producing one output value for every\ninput value.\n\n```prql no-eval\nwindow rows:(range) range:(range) expanding:false rolling:0 (pipeline)\n```\n\nFor each row, the segment over which the pipeline is applied is determined by\none of:\n\n- `rows`, which takes a range of rows relative to the current row position.\n  - `0` references the current row.\n- `range`, which takes a range of values relative to current row value.\n\nThe bounds of the range are inclusive. If a bound is omitted, the segment will\nextend until the edge of the table or group.\n\nFor ease of use, there are two flags that override `rows` or `range`:\n\n- `expanding:true` is an alias for `rows:..0`. A sum using this window is also\n  known as \"cumulative sum\".\n- `rolling:n` is an alias for `rows:(-n+1)..0`, where `n` is an integer. This\n  will include `n` last values, including current row. An average using this\n  window is also knows as a Simple Moving Average.\n\nSome examples:\n\n| Expression       | Meaning                                                            |\n| ---------------- | ------------------------------------------------------------------ |\n| `rows:0..2`      | current row plus two following                                     |\n| `rows:-2..0`     | two preceding rows plus current row                                |\n| `rolling:3`      | (same as previous)                                                 |\n| `rows:-2..4`     | two preceding rows plus current row plus four following rows       |\n| `rows:..0`       | all rows from the start of the table up to & including current row |\n| `expanding:true` | (same as previous)                                                 |\n| `rows:0..`       | current row and all following rows until the end of the table      |\n| `rows:..`        | all rows, which same as not having window at all                   |\n\n## Example\n\n```prql\nfrom employees\ngroup employee_id (\n  sort month\n  window rolling:12 (\n    derive {trail_12_m_comp = sum paycheck}\n  )\n)\n```\n\n```prql\nfrom orders\nsort day\nwindow rows:-3..3 (\n  derive {centered_weekly_average = average value}\n)\ngroup {order_month} (\n  sort day\n  window expanding:true (\n    derive {monthly_running_total = sum value}\n  )\n)\n```\n\nRows vs Range:\n\n```prql\nfrom [\n  {time_id=1, value=15},\n  {time_id=2, value=11},\n  {time_id=3, value=16},\n  {time_id=4, value=9},\n  {time_id=7, value=20},\n  {time_id=8, value=22},\n]\nwindow rows:-2..0 (\n  sort time_id\n  derive {sma3rows = average value}\n)\nwindow range:-2..0 (\n  sort time_id\n  derive {sma3range = average value}\n)\n```\n\n| time_id | value | sma3rows | sma3range |\n| ------- | ----- | -------- | --------- |\n| 1       | 15    | 15       | 15        |\n| 2       | 11    | 13       | 13        |\n| 3       | 16    | 14       | 14        |\n| 4       | 9     | 12       | 12        |\n| 7       | 20    | 15       | 20        |\n| 8       | 22    | 17       | 21        |\n\nWe can see that rows having `time_id` of 5 and 6 are missing in example data; we\ncan say there are gaps in our time series data.\n\nWhen computing SMA 3 for the fifth row (`time_id==7`) then:\n\n- \"rows\" will compute average on 3 rows (`time_id` in `3, 4, 7`)\n- \"range\" will compute average on single row only (`time_id==7`)\n\nWhen computing SMA 3 for the sixth row (`time_id==8`) then:\n\n- \"rows\" will compute average on 3 rows (`time_id` in `4, 7, 8`)\n- \"range\" will compute average on 2 rows (`time_id` in `7, 8`)\n\nWe can observe that \"rows\" ignores the content of the `time_id`, only uses its\norder; we can say its window operates on physical rows. On the other hand\n\"range\" looks at the content of the `time_id` and based on the content decides\nhow many rows fits into window; we can say window operates on logical rows.\n\n## Windowing by default\n\nIf you use window functions without `window` transform, they will be applied to\nthe whole table. Unlike in SQL, they will remain window functions and will not\ntrigger aggregation.\n\n```prql\nfrom employees\nsort age\nderive {rnk = rank age}\n```\n\nYou can also only apply `group`:\n\n```prql\nfrom employees\ngroup department (\n  sort age\n  derive {rnk = rank age}\n)\n```\n\n## Window functions as first class citizens\n\nThere are no limitations on where windowed expressions can be used:\n\n```prql\nfrom employees\nfilter salary < (average salary)\n```\n"
  },
  {
    "path": "web/book/src/reference/syntax/README.md",
    "content": "# Syntax\n\nA summary of PRQL syntax:\n\n<!-- markdownlint-disable MD033 — the `|` characters need to be escaped, and surrounded with tags rather than backticks   -->\n\n<!-- I can't seem to get \"Quoted identifies\" to work without a space between the backticks. VS Code will preview ` `` ` correctly, but not mdbook -->\n\n<!-- TODO: assigns links to select, aliases to join, potentially we should have explicit sections for them?  -->\n\n| Syntax                 | Usage                                                                          | Example                                                 |\n| ---------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------- |\n| <code>\\|</code>        | [Pipelines](./function-calls.md)                                               | <code>from employees \\| select first_name</code>        |\n| `=`                    | [Assigns](../declarations/variables.md)                                        | `from e = employees` <br> `derive total = (sum salary)` |\n| `:`                    | [Named args & parameters](../declarations/functions.md)                        | `interp low:0 1600 sat_score`                           |\n| `{}`                   | [Tuples](./tuples.md)                                                          | `{id, false, total = 3}`                                |\n| `[]`                   | [Arrays](./arrays.md)                                                          | `[1, 4, 3, 4]`                                          |\n| `+`,`!`,`&&`,`==`, etc | [Operators](./operators.md)                                                    | <code>filter a == b + c \\|\\| d >= e</code>              |\n| `()`                   | [Parentheses](./operators.md#parentheses)                                      | `derive celsius = (fht - 32) / 1.8`                     |\n| `\\`                    | [Line wrap](./operators.md#wrapping-lines)                                     | <code>1 + 2 + 3 +</code><br><code>\\ 4 + 5</code>        |\n| `1`,`100_000`,`5e10`   | [Numbers](./literals.md#numbers)                                               | `derive { huge = 5e10 * 10_000 }`                       |\n| `''`,`\"\"`              | [Strings](./literals.md#strings)                                               | `derive name = 'Mary'`                                  |\n| `true`,`false`         | [Booleans](./literals.md#booleans)                                             | `derive { Col1 = true }`                                |\n| `null`                 | [Null](./literals.md#null)                                                     | `filter ( name != null )`                               |\n| `@`                    | [Dates & times](./literals.md#date-and-time)                                   | `@2021-01-01`                                           |\n| `` ` ` ``              | [Quoted identifiers](./keywords.md#quoting)                                    | ``select `first name` ``                                |\n| `#`                    | [Comments](./comments.md)                                                      | `# A comment`                                           |\n| `==`                   | [Self-equality in `join`](../stdlib/transforms/join.md#self-equality-operator) | `join s=salaries (==id)`                                |\n| `->`                   | [Function definitions](../declarations/functions.md)                           | `let add = a b -> a + b`                                |\n| `=>`                   | [Case statement](./case.md)                                                    | `case [a==1 => c, a==2 => d]`                           |\n| `+`,`-`                | [Sort order](../stdlib/transforms/sort.md)                                     | `sort {-amount, +date}`                                 |\n| `??`                   | [Coalesce](./operators.md#coalesce)                                            | `amount ?? 0`                                           |\n\n<!-- markdownlint-enable MD033 -->\n"
  },
  {
    "path": "web/book/src/reference/syntax/arrays.md",
    "content": "# Arrays\n\nArray is a container type, composed of multiple items. All items must be of the\nsame type. Number of fields can be vary.\n\n<!-- prettier-ignore -->\n> [!WARNING]\n> This page is a stub.\n"
  },
  {
    "path": "web/book/src/reference/syntax/case.md",
    "content": "# Case\n\nSearch for the first condition that evaluates to `true` and return its\nassociated value. If none of the conditions match, `null` is returned.\n\n```prql\nfrom employees\nderive distance = case [\n  city == \"Calgary\" => 0,\n  city == \"Edmonton\" => 300,\n]\n```\n\nTo set a default, a `true` condition can be used:\n\n```prql\nfrom employees\nderive distance = case [\n  city == \"Calgary\" => 0,\n  city == \"Edmonton\" => 300,\n  true => \"Unknown\",\n]\n```\n"
  },
  {
    "path": "web/book/src/reference/syntax/comments.md",
    "content": "# Comments\n\nCharacter `#` denotes a comment until the end of the line.\n\n```prql\nfrom employees  # Comment 1\n# Comment 2\naggregate {average salary}\n```\n\nThere's no distinct multiline comment syntax.\n"
  },
  {
    "path": "web/book/src/reference/syntax/f-strings.md",
    "content": "# F-strings\n\nF-strings are a readable approach to building new strings from existing strings\n& variables.\n\n```prql\nfrom employees\nselect full_name = f\"{first_name} {last_name}\"\n```\n\nThis can be much easier to read for longer strings, relative to the SQL\napproach:\n\n```prql\nfrom web\nselect url = f\"http{tls}://www.{domain}.{tld}/{page}\"\n```\n\nNote that currently interpolations can only contain plain variable names and not\nwhole expressions like Python, so this won't work:\n\n```prql error no-fmt\nfrom tracks\nselect length_str = f\"{length_seconds / 60} minutes\"\n```\n\n## Roadmap\n\nIn the future, f-strings may incorporate string formatting such as datetimes,\nnumbers, and padding. If there's a feature that would be helpful, please\n[post an issue](https://github.com/PRQL/prql/issues/new/).\n"
  },
  {
    "path": "web/book/src/reference/syntax/function-calls.md",
    "content": "# Function calls\n\n## Simple\n\nA distinction between PRQL and most other programming languages is the function\ncall syntax. It consists of the function name followed by arguments separated by\nwhitespace.\n\n```prql no-eval\nfunction_name arg1 arg2 arg3\n```\n\nIf one of the arguments is also a function call, it must be encased in\nparentheses, so we know where arguments of inner function end and the arguments\nof outer function start.\n\n```prql no-eval\nouter_func arg_1 (inner_func arg_a, arg_b) arg_2\n```\n\nThe function name must refer to a function variable, which has either\n[been declared](../declarations/functions.md) in the\n[standard library](../stdlib/) or some other module.\n\nFunction calls can also specify named parameters using `:` notation:\n\n```prql no-eval\nfunction_name arg1 named_param:arg2 arg3\n```\n\n## Pipeline\n\nThere is a alternative way of calling functions: using a pipeline. Regardless of\nwhether the pipeline is delimited by pipe symbol `|` or a new line, the pipeline\nis equivalent to applying each of functions as the last argument of the next\nfunction.\n\n```prql no-eval\na | foo 3 | bar 'hello' 'world' | baz\n```\n\n... is equivalent to ...\n\n```prql no-eval\nbaz (bar 'hello' 'world' (foo 3 a))\n```\n\n<!--\nTODO: this should be a part of the tutorial\n\n\nAs you may have noticed, transforms are regular functions too!\n\n```prql\nfrom employees\nfilter age > 50\nsort name\n```\n\n... is equivalent to ...\n\n```prql\nfrom employees | filter age > 50 | sort name\n```\n\n... is equivalent to ...\n\n```prql\nfilter age > 50 (from employees) | sort name\n```\n\n... is equivalent to ...\n\n```prql\nsort name (filter age > 50 (from employees))\n```\n\nAs you can see, the first example with pipeline notation is much easier to\ncomprehend, compared to the last one with the regular function call notation.\nThis is why it is recommended to use pipelines for nested function calls that\nare 3 or more levels deep.\n\n-->\n"
  },
  {
    "path": "web/book/src/reference/syntax/keywords.md",
    "content": "# Identifiers & keywords\n\nIdentifiers can contain alphanumeric characters and `_` and must not start with\na number. They can be chained together with the `.` lookup operator, used to\nretrieve a tuple from a field or a variable from a module.\n\n```prql no-eval\nhello\n\n_h3llo\n\nhello.world\n```\n\n## `this` & `that`\n\n`this` refers to the current relation:\n\n```prql\nfrom invoices\naggregate (\n    count this\n)\n```\n\nWithin a [`join`](../stdlib/transforms/join.md), `that` refers to the other\ntable:\n\n```prql\nfrom invoices\njoin tracks (this.track_id==that.id)\n```\n\n`this` can also be used to remove any column ambiguity. For example, currently\nusing a bare `time` as a column name will fail, because it's also a type:\n\n```prql error no-fmt\nfrom invoices\nderive t = time\n```\n\nBut with `this.time`, we can remove the ambiguity:\n\n```prql\nfrom invoices\nderive t = this.time\n```\n\n## Quoting\n\nTo use characters that would be otherwise invalid, identifiers can be surrounded\nby with backticks.\n\nWhen compiling to SQL, these identifiers will use dialect-specific quotes and\nquoting rules.\n\n```prql\nprql target:sql.mysql\nfrom employees\nselect `first name`\n```\n\n```prql\nprql target:sql.postgres\nfrom employees\nselect `first name`\n```\n\n```prql\nprql target:sql.bigquery\n\nfrom `project-foo.dataset.table`\njoin `project-bar.dataset.table` (==col_bax)\n```\n\n## Schemas & database names\n\nIdentifiers of database tables can be prefixed with schema and databases names.\n\n```prql\nfrom my_database.chinook.albums\n```\n\nNote that all of following identifiers will be treated as separate table\ndefinitions: `tracks`, `public.tracks`, `my_database.public.tracks`.\n\n## Keywords\n\nPRQL uses following keywords:\n\n- **`prql`** - query header [_more..._](../../project/target.md)\n- **`let`** - variable definition [_more..._](../declarations/variables.md)\n- **`into`** - variable definition [_more..._](../declarations/variables.md)\n- **`case`** - flow control [_more..._](../syntax/case.md)\n- **`type`** - type declaration\n- **`func`** - explicit function declaration\n  [_more..._](../declarations/functions.md)\n- **`module`** - used internally\n- **`internal`** - used internally\n- **`true`** - boolean [_more..._](./literals.md#booleans)\n- **`false`** - boolean [_more..._](./literals.md#booleans)\n- **`null`** - NULL [_more..._](./literals.md#null)\n\nKeywords can be used as identifiers (of columns or variables) when encased in\nbackticks: `` `case` ``.\n\nTransforms are normal functions within the `std` namespace, not keywords. That\nis, `std.from` is the same function as `from`. In the example below, the\nresulting query is the same as without the `std.` namespace:\n\n```prql\nstd.from my_table\nstd.select {from = my_table.a, take = my_table.b}\nstd.take 3\n```\n"
  },
  {
    "path": "web/book/src/reference/syntax/literals.md",
    "content": "# Literals\n\nA literal is a constant value expression, with special syntax rules for each\ndata type.\n\n## Numbers\n\nNumber literals can contain number characters as well as a period, underscores\nand char `e`.\n\nIf a number literal contains a dot or character `e`, it is treated as floating\npoint number (or just _float_), otherwise it is treated as integer number.\n\nCharacter `e` denotes\n[\"scientific notation\"](https://en.wikipedia.org/wiki/Scientific_notation),\nwhere the number after `e` is the exponent in 10-base.\n\nUnderscores are ignored, so they can be placed at arbitrary positions, but it is\nadvised to use them as thousand separators.\n\nIntegers can, alternatively, be expressed using hexadecimal, octal or binary\nnotation using these prefixes respectively: `0x`, `0o` or `0b`.\n\n```prql\nfrom numbers\nselect {\n    small = 1.000_000_1,\n    big = 5_000_000,\n    huge = 5e9,\n    binary = 0b0011,\n    hex = 0x80,\n    octal = 0o777,\n}\n```\n\n## Strings\n\nPRQL supports string literals and several other formats of strings. See the\n[Strings](./strings.md) page for more information.\n\n## Booleans\n\nBoolean values can be expressed with `true` or `false` keyword.\n\n## Null\n\nThe null value can be expressed with `null` keyword. See also the discussion of\nhow [PRQL handles nulls](../spec/null.md).\n\n## Date and time\n\nDate and time literals are expressed with character `@`, followed by a string\nthat encodes the date & time.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> PRQL's notation is designed to be less verbose than SQL's\n> `TIMESTAMP '2004-10-19 10:23:54'` and more explicit than SQL's implicit option\n> that just uses a string `'2004-10-19 10:23:54'`.\n\n### Dates\n\nDates are represented by `@{yyyy-mm-dd}` — a `@` followed by the date format.\n\n```prql\nfrom employees\nderive age_at_year_end = (@2022-12-31 - dob)\n```\n\n### Times\n\nTimes are represented by `@{HH:mm:ss.SSS±Z}` with any parts not supplied\ndefaulting to zero. This includes the timezone, which is represented by\n`+HH:mm`, `-HH:mm` or `Z`. This is consistent with the ISO8601 time format.\n\n```prql\nfrom orders\nderive should_have_shipped_today = (order_time < @08:30)\n```\n\n### Timestamps\n\nTimestamps are represented by `@{yyyy-mm-ddTHH:mm:ss.SSS±Z}` / `@{date}T{time}`,\nwith any time parts not supplied being rounded to zero, including the timezone,\nwhich is represented by `+HH:mm`, `-HH:mm` or `Z` (`:` is optional). This is `@`\nfollowed by the ISO8601 datetime format, which uses `T` to separate date & time.\n\n```prql\nfrom commits\nderive first_prql_commit = @2020-01-01T13:19:55-08:00\nderive first_prql_commit_utc = @2020-01-02T21:19:55Z\n```\n\n### Durations\n\nDurations are represented by `{N}{periods}`, such as `2years` or `10minutes`,\nwithout a space.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> These aren't the same as ISO8601, because we evaluated\n> `P3Y6M4DT12H30M5S` to be difficult to understand, but we could support a\n> simplified form if there's demand for it. We don't currently support compound\n> expressions, for example `2years10months`, but most DBs will allow\n> `2years + 10months`. Please raise an issue if this is inconvenient.\n\n```prql\nfrom projects\nderive first_check_in = start + 10days\n```\n\n### Examples\n\nHere's a larger list of date and time examples:\n\n- `@20221231` is invalid — it must contain full punctuation (`-` and `:`),\n- `@2022-12-31` is a date\n- `@2022-12` or `@2022` are invalid — SQL can't express a month, only a date\n- `@16:54:32.123456` is a time\n- `@16:54:32`, `@16:54`, `@16` are all allowed, expressing `@16:54:32.000000`,\n  `@16:54:00.000000`, `@16:00:00.000000` respectively\n- `@2022-12-31T16:54:32.123456` is a timestamp without timezone\n- `@2022-12-31T16:54:32.123456Z` is a timestamp in UTC\n- `@2022-12-31T16:54+02` is timestamp in UTC+2\n- `@2022-12-31T16:54+02:00` and `@2022-12-31T16:54+02` are datetimes in UTC+2\n- `@16:54+02` is invalid — time is always local, so it cannot have a timezone\n- `@2022-12-31+02` is invalid — date is always local, so it cannot have a\n  timezone\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Currently prqlc does not parse or validate any of the datetime strings\n> and will pass them to the database engine without adjustment. This might be\n> refined in the future to aid in compatibility across databases. We'll always\n> support the canonical [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format\n> described above.\n\n### Roadmap\n\nDatetimes (as a distinct datatype from the timestamps) are supported by some\ndatabases (e.g. MySql, BigQuery). With the addition of type casts, these could\nbe represented by a timestamp cast to a datetime:\n\n```prql no-eval\nderive pi_day = @2017-03-14T15:09:26.535898<datetime>\n```\n\nThese are some examples we can then add:\n\n- `@2022-12-31T16:54<datetime>` is datetime without timezone\n- `@2022-12-31<datetime>` is forbidden — datetime must specify time\n- `@16:54<datetime>` is forbidden — datetime must specify date\n"
  },
  {
    "path": "web/book/src/reference/syntax/operators.md",
    "content": "# Operators\n\nExpressions can be composed from _function calls_ and _operations_, such as\n`2 + 3` or `((1 + x) * -y)`. In the example below, note the use of expressions\nto calculate the alias `circumference` and in the `filter` transform.\n\n```prql\nfrom foo\nselect {\n  circumference = diameter * 3.14159,\n  area = (diameter / 2) ** 2,\n  color,\n}\nfilter circumference > 10 && color != \"red\"\n```\n\n## Operator precedence\n\nThis table shows operator precedence. Use parentheses `()` to prioritize\noperations and for function calls (see the discussion below.)\n\n<!-- markdownlint-disable MD033 — the `|` characters need to be escaped, and surrounded with tags rather than backticks   -->\n\n|          Group | Operators                   | Precedence | Associativity |\n| -------------: | --------------------------- | :--------: | :-----------: |\n|    parentheses | `()`                        |     0      |   see below   |\n| identifier dot | `.`                         |     1      |               |\n|          unary | `-` `+` `!` `==`            |     2      |               |\n|          range | `..`                        |     3      |               |\n|            pow | `**`                        |     4      | right-to-left |\n|            mul | `*` `/` `//` `%`            |     5      | left-to-right |\n|            add | `+` `-`                     |     6      | left-to-right |\n|        compare | `==` `!=` `<=` `>=` `<` `>` |     7      | left-to-right |\n|       coalesce | `??`                        |     8      | left-to-right |\n|            and | `&&`                        |     9      | left-to-right |\n|             or | <code>\\|\\|</code>           |     10     | left-to-right |\n|  function call |                             |     11     |               |\n\n## Division and integer division\n\nThe `/` operator performs division that always returns a float value, while the\n`//` operator does integer division (truncated division) that always returns an\ninteger value.\n\n```prql\nprql target:sql.sqlite\n\nfrom [\n  {a = 5, b = 2},\n  {a = 5, b = -2},\n]\nselect {\n  div_out = a / b,\n  int_div_out = a // b,\n}\n```\n\n## Coalesce\n\nWe can coalesce values with an `??` operator. Coalescing takes either the first\nvalue or, if that value is null, the second value.\n\n```prql\nfrom orders\nderive amount ?? 0\n```\n\n## Regex expressions\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> This is currently experimental\n\nTo perform a case-sensitive regex search, use the `~=` operator. This generally\ncompiles to `REGEXP`, though differs by dialect. A regex search means that to\nmatch an exact value, the start and end need to be anchored with `^foo$`.\n\n```prql\nfrom tracks\nfilter (name ~= \"Love\")\n```\n\n```prql\nprql target:sql.duckdb\n\nfrom artists\nfilter (name ~= \"Love.*You\")\n```\n\n```prql\nprql target:sql.bigquery\n\nfrom tracks\nfilter (name ~= \"\\\\bLove\\\\b\")\n```\n\n```prql\nprql target:sql.postgres\n\nfrom tracks\nfilter (name ~= \"\\\\(I Can't Help\\\\) Falling\")\n```\n\n```prql\nprql target:sql.mysql\n\nfrom tracks\nfilter (name ~= \"With You\")\n```\n\n```prql\nprql target:sql.sqlite\n\nfrom tracks\nfilter (name ~= \"But Why Isn't Your Syntax More Similar\\\\?\")\n```\n\n## Parentheses\n\nPRQL uses parentheses `()` for several purposes:\n\n- Parentheses group operands to control the order of evaluation, for example:\n  `((1 + x) * y)`\n\n- Parentheses delimit a minus sign of a function argument, for example:\n  `add (-1) (-3)`\n\n- Parentheses delimit nested function calls that contain a pipe, either the `|`\n  symbol or a new line. “Nested” means within a transform; i.e. not just the\n  main pipeline, for example: `(column-name | in 0..20)`\n\n- Parentheses wrap a function call that is part of a larger expression, for\n  example: `math.round 0 (sum distance)`\n\nParentheses are _not_ required for expressions that do not contain function\ncalls, for example: `foo + bar`.\n\nHere's a set of examples of these rules:\n\n```prql\nfrom employees\n# Requires parentheses, because it contains a pipe\nderive is_proximate = (distance | in 0..20)\n# Requires parentheses, because it's a function call\nderive total_distance = (sum distance)\n# `??` doesn't require parentheses, as it's not a function call\nderive min_capped_distance = (min distance ?? 5)\n# No parentheses needed, because no function call\nderive travel_time = distance / 40\n# No inner parentheses needed around `1+1` because no function call\nderive distance_rounded_2_dp = (math.round 1+1 distance)\nderive {\n  # Requires parentheses, because it contains a pipe\n  is_far = (distance | in 100..),\n  # The left value of the range requires parentheses,\n  # because of the minus sign\n  is_negative = (distance | in (-100..0)),\n  # ...this is equivalent\n  is_negative = (distance | in (-100)..0),\n  # _Technically_, this doesn't require parentheses, because it's\n  # the RHS of an assignment in a tuple\n  # (this is especially confusing)\n  average_distance = average distance,\n}\n# Requires parentheses because of the minus sign\nsort (-distance)\n# A tuple is fine too\nsort {-distance}\n```\n\nFor example, the snippet below produces an error because the `sum` function call\nis not in a tuple.\n\n```prql error no-fmt\nfrom employees\nderive total_distance = sum distance\n```\n\n...while with parentheses, it works at expected:\n\n```prql\nfrom employees\nderive other_distance = (sum distance)\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> We're continuing to think whether these rules can be more intuitive.\n> We're also planning to make the error messages much better, so the compiler\n> can help out.\n\n## Wrapping lines\n\nLine breaks in PRQL have semantic meaning, so to wrap a single logical line into\nmultiple physical lines, we can use `\\` at the beginning of subsequent physical\nlines:\n\n```prql\nfrom artists\nselect is_europe =\n\\ country == \"DE\"\n\\ || country == \"FR\"\n\\ || country == \"ES\"\n```\n\nWrapping will \"jump over\" empty lines or lines with comments. For example, the\n`select` here is only one logical line:\n\n```prql\nfrom tracks\n# This would be a really long line without being able to split it:\nselect listening_time_years = (spotify_plays + apple_music_plays + pandora_plays)\n# We can toggle between lines when developing:\n# \\ * length_seconds\n\\ * length_s\n#   min  hour day  year\n\\ / 60 / 60 / 24 / 365\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Note that PRQL differs from most languages, which use a `\\` at the\n> _end_ of the preceding line. Because PRQL aims to be friendly for data\n> exploration, we want to make it possible to comment out any line, including\n> the final line, without breaking the query. This requires all lines after the\n> first to be structured similarly, and for the character to be at the start of\n> each following line.\n\nSee [Pipes](./pipes.md) for more details on line breaks.\n"
  },
  {
    "path": "web/book/src/reference/syntax/parameters.md",
    "content": "# Parameters\n\nParameter is a placeholder for a value provided after the compilation of the\nquery.\n\nIt uses the following syntax: `$id`, where `id` is an arbitrary alpha numeric\nstring.\n\nMost database engines only support numeric positional parameter ids (i.e `$3`).\n\n```prql\nfrom employees\nfilter id == $1\n```\n"
  },
  {
    "path": "web/book/src/reference/syntax/pipes.md",
    "content": "# Pipes\n\nPipes are the connection between [transforms](../stdlib/transforms/) that make\nup a pipeline. The relation produced by a transform before the pipe is used as\nthe input for the transform following the pipe. A pipe can be represented with\neither a line break or a pipe character (`|`).\n\nFor example, here the `filter` transform operates on the result of\n`from employees` (which is just the `employees` table), and the `select`\ntransform operates on the result of the `filter` transform.\n\n```prql\nfrom employees\nfilter department == \"Product\"\nselect {first_name, last_name}\n```\n\nIn the place of a line break, it's also possible to use the `|` character to\npipe results between transforms, such that this is equivalent:\n\n```prql\nfrom employees | filter department == \"Product\" | select {first_name, last_name}\n```\n\n## \"Ceci n'est pas une pipe\"\n\nIn almost all situations, a line break acts as a pipe. But there are a few cases\nwhere a line break doesn't act as a pipe.\n\n- before or after tuple items\n- before or after list items\n- before a new statement, which starts with `let` or `from` (or `func`)\n- within a [line wrap](./operators.md#wrapping-lines)\n\nFor example:\n\n```prql\n[\n  {a=2}      # No pipe from line break before & after this list item\n]\nderive {\n  c = 2 * a, # No pipe from line break before & after this tuple item\n}\n```\n\n```prql\nlet b =\n  \\ 3        # No pipe from line break within this line wrap\n\n# No pipe from line break before this `from` statement\n\nfrom y\nderive a = b\n```\n\n## Inner Transforms\n\n<!-- TODO: I don't think this really fits here -->\n\nParentheses are also used for transforms (such as `group` and `window`) that\npass their result to an \"inner transform\". The example below applies the\n`aggregate` pipeline to each group of unique `title` and `country` values:\n\n```prql\nfrom employees\ngroup {title, country} (\n  aggregate {\n    average salary,\n    ct = count salary,\n  }\n)\n```\n"
  },
  {
    "path": "web/book/src/reference/syntax/r-strings.md",
    "content": "# R-strings\n\nR-strings handle escape characters without special treatment:\n\n```prql\nfrom artists\nderive normal_string =  \"\\\\\\t\"   #  two characters - \\ and tab (\\t)\nderive raw_string    = r\"\\\\\\t\"   # four characters - \\, \\, \\, and t\n```\n"
  },
  {
    "path": "web/book/src/reference/syntax/ranges.md",
    "content": "# Ranges\n\nRange `start..end` represents as set of values between `start` and `end`,\ninclusive (greater of equal to `start` and less than or equal to `end`).\n\nTo express a range that is open on one side, either `start` or `end` can be\nomitted.\n\nRanges can be used in filters with the `in` function, with any type of literal,\nincluding dates:\n\n```prql\nfrom events\nfilter (created_at | in @1776-07-04..@1787-09-17)\nfilter (magnitude | in 50..100)\nderive is_northern = (latitude | in 0..)\n```\n\nRanges can also be used in `take`:\n\n```prql\nfrom orders\nsort {-value, created_at}\ntake 101..110\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Half-open ranges are generally less intuitive to read than a simple\n> `>=` or `<=` operator.\n\n## See also\n\n- [take transform](../stdlib/transforms/take.md)\n\n## Roadmap\n\nWe'd like to use ranges for other types, such as whether an object is in an\narray or list literal.\n"
  },
  {
    "path": "web/book/src/reference/syntax/s-strings.md",
    "content": "# S-strings\n\nAn s-string inserts SQL directly, as an escape hatch when there's something that\nPRQL doesn't yet implement. For example, there's a `version()` function in\nPostgreSQL that returns the PostgreSQL version, so if we want to use that, we\nuse an s-string:\n\n```prql\nfrom my_table\nselect db_version = s\"version()\"\n```\n\nEmbed a column name in an s-string using braces. For example, PRQL's standard\nlibrary defines the `average` function as:\n\n```prql no-eval\nlet average = column -> s\"AVG({column})\"\n```\n\nSo this compiles using the function:\n\n```prql\nfrom employees\naggregate {average salary}\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Because S-string contents are SQL, double-quotes (`\"`) will denote a\n> _column name_. To avoid that, use single-quotes (`'`) around the SQL string,\n> and adjust the quotes of the S-string. For example, instead of\n> `s'CONCAT(\"hello\", \"world\")'` use `s\"CONCAT('hello', 'world')\"`\n\nHere's an example of a more involved use of an s-string:\n\n```prql\nfrom de=dept_emp\njoin s=salaries side:left (s.emp_no == de.emp_no && s\"\"\"\n  ({s.from_date}, {s.to_date})\n  OVERLAPS\n  ({de.from_date}, {de.to_date})\n\"\"\")\n```\n\nFor those who have used Python, s-strings are similar to Python's f-strings, but\nthe result is SQL code, rather than a string literal. For example, a Python\nf-string of `f\"average({col})\"` would produce `\"average(salary)\"`, with quotes;\nwhile in PRQL, `s\"average({col})\"` produces `average(salary)`, without quotes.\n\nNote that interpolations can only contain plain variable names and not whole\nexpression like Python.\n\nWe can also use s-strings to produce a full table:\n\n```prql\nfrom s\"SELECT DISTINCT ON first_name, id, age FROM employees ORDER BY age ASC\"\njoin s = s\"SELECT * FROM salaries\" (==id)\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> S-strings in user code are intended as an escape hatch for an\n> unimplemented feature. If we often need s-strings to express something, that's\n> a sign we should implement it in PRQL or PRQL's stdlib. If you often require\n> an s-string,\n> [submit an issue with your use case](https://github.com/PRQL/prql/issues/new/choose).\n\n## Braces\n\nTo output braces from an s-string, use double braces:\n\n```prql\nfrom employees\nderive {\n  has_valid_title = s\"regexp_contains(title, '([a-z0-9]*-){{2,}}')\"\n}\n```\n\n## Precedence within s-strings\n\nVariables in s-strings are inserted into the SQL source as-is, which means we\nmay get surprising behavior when the variable has multiple terms and the\ns-string isn't parenthesized.\n\nIn this toy example, the expression `salary + benefits / 365` gets precedence\nwrong. The generated SQL code is as if we had written\n`salary + (benefits / 365)`.\n\n```prql\nfrom employees\nderive {\n  gross_salary = salary + benefits,\n  daily_rate = s\"{gross_salary} / 365\"\n}\n```\n\nInstead, the numerator `{gross_salary}` must be encased in parentheses:\n\n```prql\nfrom employees\nderive {\n  gross_salary = salary + benefits,\n  daily_rate = s\"({gross_salary}) / 365\"\n}\n```\n"
  },
  {
    "path": "web/book/src/reference/syntax/strings.md",
    "content": "# Strings\n\nString literals can use any matching odd number of either single or double\nquotes:\n\n```prql\nfrom artists\nderive {\n  single        =   'hello world',\n  double        =   \"hello world\",\n  double_triple = \"\"\"hello world\"\"\",\n}\n```\n\n## Quoting and escape characters\n\nTo quote a string containing quote characters, use the \"other\" type of quote, or\nuse the escape character `\\`, or use more quotes.\n\n```prql\nfrom artists\nselect {\n  other   = '\"hello world\"',\n  escaped = \"\\\"hello world\\\"\",\n  triple  = \"\"\"I said \"hello world\"!\"\"\",\n}\n```\n\nStrings can contain any escape character sequences defined by the\n[JSON standard](https://www.ecma-international.org/publications-and-standards/standards/ecma-404/).\n\n```prql\nfrom artists\nderive escapes = \"\\tXYZ\\n \\\\ \"                            # tab (\\t), \"XYZ\", newline (\\n), \" \", \\, \" \"\nderive world = \"\\u{0048}\\u{0065}\\u{006C}\\u{006C}\\u{006F}\" # \"Hello\"\nderive hex = \"\\x48\\x65\\x6C\\x6C\\x6F\"                       # \"Hello\"\nderive turtle = \"\\u{01F422}\"                              # \"🐢\"\n```\n\n## Other string formats\n\n- [**F-strings**](./f-strings.md) - Build up a new string from a set of columns\n  or values.\n- [**R-strings**](./r-strings.md) - Include the raw characters of the string\n  without any form of escaping.\n- [**S-strings**](./s-strings.md) - Insert SQL statements directly into the\n  query. Use when PRQL doesn't have an equivalent facility.\n\n<!-- prettier-ignore -->\n<!-- prettier-ignore -->\n> [!WARNING]\n> Currently PRQL allows multiline strings with either a single\n> character or multiple character quotes. This may change for strings using a\n> single character quote in future versions.\n\n<!-- prettier-ignore -->\n<!-- prettier-ignore -->\n> [!NOTE]\n> These escape rules specify how PRQL interprets escape characters when\n> compiling strings to SQL, not necessarily how the database will interpret the\n> string. Dialects interpret escape characters differently, and PRQL doesn't\n> currently account for these differences. Please open issues with any\n> difficulties in the current implementation.\n\n## Escape sequences\n\nUnless an `r` prefix is present, escape sequences in string literals are\ninterpreted according to rules similar to those used by Standard C. The\nrecognized escape sequences are:\n\n| Escape Sequence | Meaning                       |\n| --------------- | ----------------------------- |\n| `\\\\`            | Backslash (\\)                 |\n| `\\'`            | Single quote (')              |\n| `\\\"`            | Double quote (\")              |\n| `\\b`            | Backspace                     |\n| `\\f`            | Formfeed                      |\n| `\\n`            | ASCII Linefeed (LF)           |\n| `\\r`            | ASCII Carriage Return (CR)    |\n| `\\t`            | ASCII Horizontal Tab (TAB)    |\n| `\\xhh`          | Character with hex value hh   |\n| `\\u{xxxx}`      | Character with hex value xxxx |\n"
  },
  {
    "path": "web/book/src/reference/syntax/tuples.md",
    "content": "# Tuples\n\nTuple is a container type, composed of multiple fields. Each field can have a\ndifferent type. Number of fields and their types must be known at compile time.\n\nTuple is represented by `{}`. It can span multiple lines. Fields can be assigned\na name. Fields are separated by commas, trailing trailing comma is optional.\n\n```prql no-eval\nlet var1 = {x = 1, y = 2}\n\nlet var2 = {           # Span multiple lines\n  a = x,\n  b = y                # Optional trailing comma\n}\n\nlet var3 = {\n  c,                   # Individual item\n  d = b,               # Assignment\n}\n```\n\nTuples are the type of a table row, which means that they are expected by many\ntransforms. Most transforms can also take a single field, which will be\nconverted into a tuple. These are equivalent:\n\n```prql\nfrom employees\nselect {first_name}\n```\n\n```prql\nfrom employees\nselect first_name\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Prior to `0.9.0`, tuples were previously named Lists, and represented\n> with `[]` syntax. There may still be references to the old naming.\n"
  },
  {
    "path": "web/book/src/tutorial/aggregation.md",
    "content": "# Aggregation\n\nA key feature of analytics is reducing many values down to a summary. This act\nis called \"aggregation\" and always includes a function &mdash; for example,\n`average` or `sum` &mdash; that reduces values in the table to a single row.\n\n### `aggregate` transform\n\nThe `aggregate` transform takes a tuple to create one or more new columns that\n\"distill down\" data from all the rows.\n\n```prql no-eval\nfrom invoices\naggregate { sum_of_orders = sum total }\n```\n\nThe query above computes the sum of the `total` column of all rows of the\n`invoices` table to produce a single value.\n\n`aggregate` can produce multiple summaries at once when one or more aggregation\nexpressions are contained in a tuple. `aggregate` discards all columns that are\nnot present in the tuple.\n\n```prql no-eval\nfrom invoices\naggregate {\n    num_orders = count this,\n    sum_of_orders = sum total,\n}\n```\n\nIn the example above, the result is a single row with two columns. The `count`\nfunction displays the number of rows in the table that was passed in; the `sum`\nfunction adds up the values of the `total` column of all rows.\n\n## Grouping\n\nSuppose we want to produce summaries of invoices _for each city_ in the table.\nWe could create a query for each city, and aggregate its rows:\n\n```prql no-eval\nfrom invoices\nfilter billing_city == \"Oslo\"\naggregate { sum_of_orders = sum total }\n```\n\nBut we would need to do it for each city: `London`, `Frankfurt`, etc. Of course\nthis is repetitive (and boring) and error prone (because we would need to type\neach `billing_city` by hand). Moreover, we would need to create a list of each\n`billing_city` before we started.\n\n### `group` transform\n\nThe `group` transform separates the table into groups (say, those having the\nsame city) using information that's already in the table. It then applies a\ntransform to each group, and combines the results back together:\n\n```prql no-eval\nfrom invoices\ngroup billing_city (\n    aggregate {\n        num_orders = count this,\n        sum_of_orders = sum total,\n    }\n)\n```\n\nThose familiar with SQL have probably noticed that we just decoupled aggregation\nfrom grouping.\n\nAlthough these operations are connected in SQL, PRQL makes it straightforward to\nuse `group` and `aggregate` separate from each other, while combining with other\ntransform functions, such as:\n\n```prql no-eval\nfrom invoices\ngroup billing_city (\n    take 2\n)\n```\n\nThis code collects the first two rows for each city's `group`.\n"
  },
  {
    "path": "web/book/src/tutorial/annotated_example.md",
    "content": "# Annotated example\n\nThe [Playground](https://prql-lang.org/playground) defaults to showing a query\nthat demonstrates most of the transforms and capabilities of PRQL. This page\nexplains the details of each line in that example.\n\n<!--\n  This is the full query from the Playground. It needs to be explained line by line\n  It is not currently linked into the SUMMARY.md page\n-->\n\n```prql no-eval\nfrom invoices                        # A PRQL query begins with a table\n                                     # Subsequent lines \"transform\" (modify) it\nderive {                             # \"derive\" adds columns to the result\n  transaction_fee = 0.8,             # \"=\" sets a column name\n  income = total - transaction_fee   # Calculations can use other column names\n}\n# This is a comment; commenting out a line leaves a valid query\nfilter income > 5                    # \"filter\" replaces both of SQL's WHERE & HAVING\nfilter invoice_date >= @2010-01-16   # Clear date syntax\ngroup customer_id (                  # \"group\" performs the pipeline in (...) on each group\n  aggregate {                        # \"aggregate\" reduces each group to a single row\n    sum_income = sum income,         # ... using SQL SUM(), COUNT(), etc. functions\n    ct = count customer_id,          #\n  }\n)\njoin c=customers (==customer_id)     # join on \"customer_id\" from both tables\nderive name = f\"{c.last_name}, {c.first_name}\" # F-strings like Python\nderive db_version = s\"version()\"     # S-string offers escape hatch to SQL\nselect {                             # \"select\" passes along only the named columns\n  c.customer_id, name, sum_income, ct, db_version,\n}                                    # trailing commas always ignored\nsort {-sum_income}                   # \"sort\" sorts the result; \"-\" is decreasing order\ntake 1..10                           # Limit to a range - could also be \"take 10\"\n#\n# The \"output.sql\" tab at right shows the SQL generated from this PRQL query\n# The \"output.arrow\" tab shows the result of the query\n```\n"
  },
  {
    "path": "web/book/src/tutorial/filtering.md",
    "content": "# Filtering rows\n\nIn the previous page we learned how `select`, `derive`, and `join` change the\ncolumns of a table.\n\nNow we will explore how to manipulate the rows of a table using `filter` and\n`take`.\n\n### `filter` transform\n\nThe `filter` transform picks rows to pass through based on their values:\n\n```prql no-eval\nfrom invoices\nfilter billing_city == \"Berlin\"\n```\n\nThe resulting table contains all the rows that came from Berlin.\n\nPRQL converts the single `filter` transform to use the appropriate SQL `WHERE`\nor `HAVING` command, depending on where it appears in the pipeline.\n\n### `take` transform\n\nThe `take` transform picks rows to pass through based on their position within\nthe table. The set of rows picked can be specified in two ways:\n\n- a plain number `x`, which will pick the first `x` rows, or\n- an inclusive range of rows `start..end`.\n\n```prql no-eval\nfrom invoices\ntake 4\n```\n\n```prql no-eval\nfrom invoices\ntake 4..7\n```\n\nOf course, it is possible combine all these transforms into a single pipeline:\n\n```prql no-eval\nfrom invoices\n\n# retain only rows for orders from Berlin\nfilter billing_city == \"Berlin\"\n\n# skip first 10 rows and take the next 10\ntake 11..20\n\n# take only first 3 rows of *that* result\ntake 3\n```\n\nWe did something a bit odd at the end: first we took rows `11..20` and then took\nthe first 3 rows from that result.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Note that a single transform `take 11..13` would have produced the\n> same SQL. The example shows how PRQL allows fast data exploration by\n> \"stacking\" transforms in the pipeline. This reduces the cognitive burden:\n> unlike SQL, each new transform interacts _only_ with the results of the\n> previous query.\n"
  },
  {
    "path": "web/book/src/tutorial/relations.md",
    "content": "# Relations\n\nPRQL is designed on top of _relational algebra_, which is the established data\nmodel used by modern SQL databases. A _relation_ has a rigid mathematical\ndefinition, which can be simplified to \"a table of data\". For example, the\n`invoices` table from the Chinook database\n([https://github.com/lerocha/chinook-database](https://github.com/lerocha/chinook-database))\nlooks like this:\n\n| invoice_id | customer_id | billing_city | _other columns_ | total |\n| ---------- | ----------- | ------------ | :-------------: | ----- |\n| 1          | 2           | Stuttgart    |       ...       | 1.98  |\n| 2          | 4           | Oslo         |       ...       | 3.96  |\n| 3          | 8           | Brussels     |       ...       | 5.94  |\n| 4          | 14          | Edmonton     |       ...       | 8.91  |\n| 5          | 23          | Boston       |       ...       | 13.86 |\n| 6          | 37          | Frankfurt    |       ...       | 0.99  |\n\nA relation is composed of rows. Each row in a relation contains a value for each\nof the relation's columns. Each column in a relation has a unique name and a\ndesignated data type. The table above is a relation, and has columns named\n`invoice_id`and `customer_id` each with a data type of \"integer number\", a\n`billing_city` column with a data type of \"text\", several other columns, and a\n`total` column that contains floating-point numbers.\n\n## Queries\n\nThe main purpose of PRQL is to build queries that combine and transform data\nfrom relations such as the `invoices` table above. Here is the most basic query:\n\n```prql no-eval\nfrom invoices\n```\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Try each of these examples here in the\n> [Playground.](https://prql-lang.org/playground/) Enter the query on the\n> left-hand side, and click the **Query Results** tab on the right-hand side to\n> see the result.\n\nThe result of the query above is not terribly interesting, it's just the same\nrelation as before.\n\n### `select` transform\n\nThe `select` function picks the columns to pass through based on a list and\ndiscards all others. Formally, that list is a _tuple_ of comma-separated\nexpressions wrapped in `{ ... }`.\n\nSuppose we only need the `order_id` and `total` columns. Use `select` to choose\nthe columns to pass through. _(Try it in the\n[Playground.](https://prql-lang.org/playground/))_\n\n```prql no-eval\nfrom invoices\nselect { order_id, total }\n```\n\nWe can write the items in the tuple on one or several lines: trailing commas are\nignored. In addition, we can assign any of the expressions to a _variable_ that\nbecomes the name of the resulting column in the SQL output.\n\n```prql no-eval\nfrom invoices\nselect {\n  OrderID = invoice_id,\n  Total = total,\n}\n```\n\nThis is the same query as above, rewritten on multiple lines, and assigning\n`OrderID` and `Total` names to the columns.\n\nOnce we `select` certain columns, subsequent transforms will have access only to\nthose columns named in the tuple.\n\n### `derive` transform\n\nTo add columns to a relation, we can use the `derive` function. Let's define a\nnew column for Value Added Tax, set at 19% of the invoice total.\n\n```prql no-eval\nfrom invoices\nderive { VAT = total * 0.19 }\n```\n\n<!-- todo: make sure that the new column is unnamed -->\n\nThe value of the new column can be a constant (such as a number or a string), or\ncan be computed from the value of an existing column. Note that the value of the\nnew column is assigned the name `VAT`.\n\n### `join` transform\n\nThe `join` transform also adds columns to the relation by combining the rows\nfrom two relations \"side by side\". To determine which rows from each relation\nshould be joined, `join` has match criteria, written in `( ... )`.\n\n```prql no-eval\nfrom invoices\njoin customers ( ==customer_id )\n```\n\nThis example \"connects\" the customer information from the `customers` relation\nwith the information from the `invoices` relation, using identical values of the\n`customer_id` column from each relation to match the rows.\n\nIt is frequently useful to assign an alias to both relations being joined\ntogether so that each relation's columns can be referred to uniquely.\n\n```prql no-eval\nfrom inv=invoices\njoin cust=customers ( ==customer_id )\n```\n\nIn the example above, the alias `inv` represents the `invoices` relation and\n`cust` represents the `customers` relation. It then becomes possible to refer to\n`inv.billing_city` and `cust.last_name` unambiguously.\n\n### Summary\n\nPRQL manipulates relations (tables) of data. The `derive`, `select`, and `join`\ntransforms change the number of columns in a table. The first two never affect\nthe number of rows in a table. `join` may change the number of rows, depending\non the chosen type of join.\n\nThis final example combines the above into a single query. It illustrates _a\npipeline_ - the fundamental basis of PRQL. We simply add new lines (transforms)\nat the end of the query. Each transform modifies the relation produced by the\nstatement above to produce the desired result.\n\n```prql no-eval\nfrom inv=invoices\njoin cust=customers (==customer_id)\nderive { VAT = inv.total * 0.19 }\nselect {\n  OrderID = inv.invoice_id,\n  CustomerName = cust.last_name,\n  Total = inv.total,\n  VAT,\n}\n```\n\n<!-- PRQL uses the data from... _Where does our data come from? Do we use some canonical version?_ -->\n"
  },
  {
    "path": "web/book/tests/documentation/README.md",
    "content": "# PRQL documentation tests\n\nThis directory contains tests for PRQL documentation (website, book and README).\n"
  },
  {
    "path": "web/book/tests/documentation/book.rs",
    "content": "#![cfg(not(target_family = \"wasm\"))]\nuse std::fs;\nuse std::path::Path;\n\nuse anyhow::{anyhow, bail, Result};\nuse globset::Glob;\nuse insta::assert_snapshot;\nuse itertools::Itertools;\nuse mdbook_prql::{code_block_lang_tags, LangTag};\nuse prqlc::{pl_to_prql, pl_to_rq, prql_to_pl};\nuse pulldown_cmark::Tag;\nuse walkdir::WalkDir;\n\nuse super::compile;\n\n/// This test:\n/// - Extracts PRQL code blocks from the book\n/// - Compiles them to SQL, comparing to a snapshot.\n/// - We raise an error if they shouldn't pass or shouldn't fail.\n/// - Insta raises an error if there's a snapshot diff.\n///\n/// This mirrors the process in [`replace_examples`], which inserts a comparison\n/// table of SQL into the book, and so serves as a snapshot test of those\n/// examples.\n//\n// We re-use the code (somewhat copy-paste) for the other compile tests below.\n#[test]\nfn test_prql_examples_compile() -> Result<()> {\n    // Override the version so the examples work with the version defined in the\n    // manifest.\n    std::env::set_var(\"PRQL_VERSION_OVERRIDE\", env!(\"CARGO_PKG_VERSION\"));\n    let examples = collect_book_examples()?;\n\n    let mut errs = Vec::new();\n    for Example { name, tags, prql } in examples {\n        let result = compile(&prql);\n        let should_succeed = !tags.contains(&LangTag::Error);\n\n        match (should_succeed, result) {\n            (true, Err(e)) => errs.push(format!(\n                \"\n---- {name} ---- ERROR\nUse `prql error` as the language label to assert an error compiling the PRQL.\n\n-- Original PRQL --\n```\n{prql}\n```\n\n-- Error --\n```\n{e}\n```\n\"\n            )),\n\n            (false, Ok(output)) => errs.push(format!(\n                \"\n---- {name} ---- UNEXPECTED SUCCESS\nSucceeded compiling, but example was marked as `error`.\nRemove `error` as a language label to assert successfully compiling.\n\n-- Original PRQL --\n```\n{prql}\n```\n\n-- Result --\n```\n{output}\n```\n\"\n            )),\n            (_, result) => {\n                assert_snapshot!(name, result.unwrap_or_else(|e| e.to_string()), &prql);\n            }\n        }\n    }\n    if errs.is_empty() {\n        Ok(())\n    } else {\n        Err(anyhow!(errs.join(\"\\n\")))\n    }\n}\n\n#[test]\nfn test_prql_examples_rq_serialize() -> Result<()> {\n    for Example { tags, prql, .. } in collect_book_examples()? {\n        // Don't assert that this fails, whether or not they compile to RQ is\n        // undefined.\n        if tags.contains(&LangTag::Error) {\n            continue;\n        }\n        let rq = prql_to_pl(&prql).map(pl_to_rq)?;\n        // Serialize\n        serde_json::to_string(&rq).unwrap();\n    }\n\n    Ok(())\n}\n\n/// Test that the formatted result (the `Display` result) of each example can be\n/// compiled.\n//\n// We previously snapshot all the queries. But that was a lot of output, for\n// something we weren't yet looking at.\n//\n// The ideal would be to auto-format the examples themselves, likely during the\n// compilation. For that to provide a good output, we need to implement a proper\n// autoformatter.\n#[test]\nfn test_prql_examples_display_then_compile() -> Result<()> {\n    let examples = collect_book_examples()?;\n\n    let mut errs = Vec::new();\n    for Example { name, tags, prql } in examples {\n        let result = prql_to_pl(&prql)\n            .and_then(|x| pl_to_prql(&x))\n            .and_then(|x| compile(&x));\n\n        let should_succeed = !tags.contains(&LangTag::NoFmt);\n\n        match (should_succeed, result) {\n            (true, Err(e)) => errs.push(format!(\n                \"\n---- {name} ---- ERROR formatting & compiling\nUse `prql no-fmt` as the language label to assert an error from formatting & compiling.\n\n-- Original PRQL --\n\n```\n{prql}\n```\n-- Error --\n```\n{e}\n```\n\"\n            )),\n\n            (false, Ok(output)) => errs.push(format!(\n                \"\n---- {name} ---- UNEXPECTED SUCCESS after formatting\nSucceeded at formatting and then compiling the prql, but example was marked as `no-fmt`.\nRemove `no-fmt` as a language label to assert successfully compiling the formatted result.\n\n-- Original PRQL --\n```\n{prql}\n```\n-- Result --\n```\n{output}\n```\n\"\n            )),\n            _ => {}\n        }\n    }\n    if errs.is_empty() {\n        Ok(())\n    } else {\n        Err(anyhow!(errs.join(\"\")))\n    }\n}\n\nstruct Example {\n    /// Name contains the file, the heading, and the index of the example.\n    name: String,\n    tags: Vec<LangTag>,\n    /// The PRQL text\n    prql: String,\n}\n\n/// Collect all the PRQL examples in the book, as [Example]s.\n/// Excludes any with a `no-eval` tag.\nfn collect_book_examples() -> Result<Vec<Example>> {\n    use pulldown_cmark::{Event, Parser};\n    let glob = Glob::new(\"**/*.md\")?.compile_matcher();\n    Ok(WalkDir::new(Path::new(\"./src/\"))\n        .into_iter()\n        .flatten()\n        .filter(|x| glob.is_match(x.path()))\n        .flat_map(|dir_entry| {\n            let text = fs::read_to_string(dir_entry.path())?;\n            // TODO: Still slightly duplicative logic here and in\n            // [lib.rs/replace_examples], but not sure how to avoid it.\n            //\n            let mut parser = Parser::new(&text);\n            let mut prql_blocks: Vec<Example> = vec![];\n            // Keep track of the latest heading, so snapshots can have the\n            // section they're in. This makes them easier to find and means\n            // adding one example at the top of the book doesn't cause a huge\n            // diff in the snapshots of that file's examples..\n            let mut latest_heading = \"\".to_string();\n            let file_name = &dir_entry\n                .path()\n                .strip_prefix(\"./src/\")?\n                .to_str()\n                .unwrap()\n                .trim_end_matches(\".md\");\n\n            // Iterate through the markdown file, getting examples.\n            while let Some(event) = parser.next() {\n                if let Event::Start(Tag::Heading { .. }) = event.clone() {\n                    if let Some(Event::Text(pulldown_cmark::CowStr::Borrowed(heading))) =\n                        parser.next()\n                    {\n                        // We clear and then push because just setting\n                        // `latest_heading` leads to lifetime issues.\n                        latest_heading = heading\n                            .chars()\n                            .filter(|&c| c.is_ascii_alphanumeric() || c == '-' || c == ' ')\n                            .collect();\n                    }\n                }\n                let Some(tags) = code_block_lang_tags(&event) else {\n                    continue;\n                };\n\n                if tags.contains(&LangTag::Prql) && !tags.contains(&LangTag::NoEval) {\n                    let mut prql = String::new();\n                    while let Some(Event::Text(line)) = parser.next() {\n                        prql.push_str(line.to_string().as_str());\n                    }\n                    if prql.is_empty() {\n                        bail!(\"Expected text in PRQL code block\");\n                    }\n                    let heading = latest_heading.replace(' ', \"-\").to_ascii_lowercase();\n                    // Only add the heading if it's different from the file name.\n                    let name = if !file_name.ends_with(&heading) {\n                        format!(\"{file_name}/{heading}\")\n                    } else {\n                        file_name.to_string()\n                    };\n                    prql_blocks.push(Example { name, tags, prql });\n                }\n            }\n            Ok(prql_blocks)\n        })\n        .flatten()\n        // Add an index suffix to each path's examples (so we group by the path).\n        .chunk_by(|e| e.name.clone())\n        .into_iter()\n        .flat_map(|(path, blocks)| {\n            blocks.into_iter().enumerate().map(move |(i, e)| Example {\n                name: format!(\"{path}/{i}\"),\n                ..e\n            })\n        })\n        .collect())\n}\n"
  },
  {
    "path": "web/book/tests/documentation/main.rs",
    "content": "#![cfg(not(target_family = \"wasm\"))]\n\n/// As well as the examples in the book, we also test the examples in the\n/// website & README in this integration test binary.\nmod book;\nmod readme;\nmod website;\n\nuse ::prqlc::Options;\n\nfn compile(prql: &str) -> Result<String, prqlc::ErrorMessages> {\n    prqlc::compile(\n        prql,\n        &Options::default()\n            .no_signature()\n            .with_display(prqlc::DisplayOptions::Plain),\n    )\n}\n"
  },
  {
    "path": "web/book/tests/documentation/readme.rs",
    "content": "use regex::Regex;\n\nuse super::compile;\n\n#[test]\nfn test_readme_examples() {\n    let contents = include_str!(\"../../../../README.md\");\n    // Similar to code at https://github.com/PRQL/prql/blob/65706a115a84997c608eaeda38b1aef1240fcec3/web/book/tests/snapshot.rs#L152, but specialized for the Readme.\n    let re = Regex::new(r\"(?s)```(elm|prql)\\r?\\n(?P<prql>.+?)\\r?\\n```\").unwrap();\n    assert_ne!(re.find_iter(contents).count(), 0);\n    re.captures_iter(contents).for_each(|capture| {\n        compile(&capture[\"prql\"]).unwrap();\n    });\n}\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__README__prql-language-book__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from tracks\\nfilter artist == \\\"Bob Marley\\\"  # Each line transforms the previous result\\naggregate {                    # `aggregate` reduces each column to a value\\n  plays    = sum plays,\\n  longest  = max length,\\n  shortest = min length,       # Trailing commas are allowed\\n}\\n\"\n---\nSELECT\n  COALESCE(SUM(plays), 0) AS plays,\n  MAX(length) AS longest,\n  MIN(length) AS shortest\nFROM\n  tracks\nWHERE\n  artist = 'Bob Marley'\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__README__prql-language-book__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nfilter start_date > @2021-01-01            # Clear date syntax\\nderive {                                   # `derive` adds columns / variables\\n  gross_salary = salary + (tax ?? 0),      # Terse coalesce\\n  gross_cost = gross_salary + benefits,    # Variables can use other variables\\n}\\nfilter gross_cost > 0\\ngroup {title, country} (                   # `group` runs a pipeline over each group\\n  aggregate {                              # `aggregate` reduces each group to a value\\n    average gross_salary,\\n    sum_gross_cost = sum gross_cost,       # `=` sets a column name\\n  }\\n)\\nfilter sum_gross_cost > 100_000            # `filter` replaces both of SQL's `WHERE` & `HAVING`\\nderive id = f\\\"{title}_{country}\\\"           # F-strings like Python\\nderive country_code = s\\\"LEFT(country, 2)\\\"  # S-strings permit SQL as an escape hatch\\nsort {sum_gross_cost, -country}            # `-country` means descending order\\ntake 1..20                                 # Range expressions (also valid as `take 20`)\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    title,\n    country,\n    AVG(salary + COALESCE(tax, 0)) AS _expr_0,\n    COALESCE(SUM(salary + COALESCE(tax, 0) + benefits), 0) AS sum_gross_cost\n  FROM\n    employees\n  WHERE\n    start_date > DATE '2021-01-01'\n    AND salary + COALESCE(tax, 0) + benefits > 0\n  GROUP BY\n    title,\n    country\n)\nSELECT\n  title,\n  country,\n  _expr_0,\n  sum_gross_cost,\n  CONCAT(title, '_', country) AS id,\n  LEFT(country, 2) AS country_code\nFROM\n  table_0\nWHERE\n  sum_gross_cost > 100000\nORDER BY\n  sum_gross_cost,\n  country DESC\nLIMIT\n  20\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__project__target__examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.postgres\\n\\nfrom employees\\nsort age\\ntake 10\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nORDER BY\n  age\nLIMIT\n  10\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__project__target__examples__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.mssql\\n\\nfrom employees\\nsort age\\ntake 10\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nORDER BY\n  age OFFSET 0 ROWS\nFETCH FIRST\n  10 ROWS ONLY\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__project__target__version__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql version:\\\"0.13.4\\\"\\n\\nfrom employees\\n\"\n---\nSELECT\n  *\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__project__target__version__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"[{version = prql.version}]\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    '0.13.12' AS version\n)\nSELECT\n  version\nFROM\n  table_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__from__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from artists\\n\"\n---\nSELECT\n  *\nFROM\n  artists\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__from__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from e = employees\\nselect e.first_name\\n\"\n---\nSELECT\n  first_name\nFROM\n  employees AS e\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__from__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from `artist tracks`\\n\"\n---\nSELECT\n  *\nFROM\n  \"artist tracks\"\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__from__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"default_db.group  # in place of `from group`\\ntake 1\\n\"\n---\nSELECT\n  *\nFROM\n  \"group\"\nLIMIT\n  1\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__read-files__reading-files__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.duckdb\\n\\nfrom a = (read_parquet \\\"artists.parquet\\\")\\njoin b = (read_csv \\\"albums.csv\\\") (a.artist_id == b.artist_id)\\njoin c = (read_json \\\"metadata.json\\\") (a.artist_id == c.artist_id)\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    *\n  FROM\n    read_parquet(\n      'artists.parquet',\n      binary_as_string = false,\n      file_row_number = false,\n      hive_partitioning = NULL,\n      union_by_name = false\n    )\n),\ntable_1 AS (\n  SELECT\n    *\n  FROM\n    read_csv_auto('albums.csv')\n),\ntable_2 AS (\n  SELECT\n    *\n  FROM\n    read_json_auto('metadata.json')\n)\nSELECT\n  table_0.*,\n  table_1.*,\n  table_2.*\nFROM\n  table_0\n  INNER JOIN table_1 ON table_0.artist_id = table_1.artist_id\n  INNER JOIN table_2 ON table_0.artist_id = table_2.artist_id\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__read-files__reading-files__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from `artists.parquet`\\n\"\n---\nSELECT\n  *\nFROM\n  \"artists.parquet\"\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__relation-literals__array-literals__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from [\\n  {a=5, b=false},\\n  {a=6, b=true},\\n]\\nfilter b == true\\nselect a\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    5 AS a,\n    false AS b\n  UNION\n  ALL\n  SELECT\n    6 AS a,\n    true AS b\n)\nSELECT\n  a\nFROM\n  table_0\nWHERE\n  b = true\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__relation-literals__array-literals__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let my_artists = [\\n  {artist=\\\"Miles Davis\\\"},\\n  {artist=\\\"Marvin Gaye\\\"},\\n  {artist=\\\"James Brown\\\"},\\n]\\n\\nfrom artists\\njoin my_artists (==artist)\\njoin albums (==artist_id)\\nselect {artists.artist_id, albums.title}\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    'Miles Davis' AS artist\n  UNION\n  ALL\n  SELECT\n    'Marvin Gaye' AS artist\n  UNION\n  ALL\n  SELECT\n    'James Brown' AS artist\n),\nmy_artists AS (\n  SELECT\n    artist\n  FROM\n    table_0\n)\nSELECT\n  artists.artist_id,\n  albums.title\nFROM\n  artists\n  INNER JOIN my_artists ON artists.artist = my_artists.artist\n  INNER JOIN albums ON artists.artist_id = albums.artist_id\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__relation-literals__array-literals__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from_text \\\"\\\"\\\"\\na,b,c\\n1,2,3\\n4,5,6\\n\\\"\\\"\\\"\\nderive {\\n    d = b + c,\\n    answer = 20 * 2 + 2,\\n}\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    '1' AS a,\n    '2' AS b,\n    '3' AS c\n  UNION\n  ALL\n  SELECT\n    '4' AS a,\n    '5' AS b,\n    '6' AS c\n)\nSELECT\n  a,\n  b,\n  c,\n  b + c AS d,\n  20 * 2 + 2 AS answer\nFROM\n  table_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__relation-literals__array-literals__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from_text format:json \\\"\\\"\\\"\\n[\\n    {\\\"a\\\": 1, \\\"m\\\": \\\"5\\\"},\\n    {\\\"a\\\": 4, \\\"n\\\": \\\"6\\\"}\\n]\\n\\\"\\\"\\\"\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    1 AS a,\n    '5' AS m\n  UNION\n  ALL\n  SELECT\n    4 AS a,\n    NULL AS m\n)\nSELECT\n  a,\n  m\nFROM\n  table_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__data__relation-literals__array-literals__4.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from_text format:json \\\"\\\"\\\"\\n{\\n    \\\"columns\\\": [\\\"a\\\", \\\"b\\\", \\\"c\\\"],\\n    \\\"data\\\": [\\n        [1, \\\"x\\\", false],\\n        [4, \\\"y\\\", null]\\n    ]\\n}\\n\\\"\\\"\\\"\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    1 AS a,\n    'x' AS b,\n    false AS c\n  UNION\n  ALL\n  SELECT\n    4 AS a,\n    'y' AS b,\n    NULL AS c\n)\nSELECT\n  a,\n  b,\n  c\nFROM\n  table_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let fahrenheit_to_celsius = temp -> (temp - 32) / 1.8\\n\\nfrom cities\\nderive temp_c = (fahrenheit_to_celsius temp_f)\\n\"\n---\nSELECT\n  *,\n  (temp_f - 32) / 1.8 AS temp_c\nFROM\n  cities\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let interp = low:0 high x -> (x - low) / (high - low)\\n\\nfrom students\\nderive {\\n  sat_proportion_1 = (interp 1600 sat_score),\\n  sat_proportion_2 = (interp low:0 1600 sat_score),\\n}\\n\"\n---\nSELECT\n  *,\n  (sat_score - 0) / (1600 - 0) AS sat_proportion_1,\n  (sat_score - 0) / (1600 - 0) AS sat_proportion_2\nFROM\n  students\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__late-binding__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let cost_share = cost -> cost / cost_total\\n\\nfrom costs\\nselect {materials, labor, overhead, cost_total}\\nderive {\\n  materials_share = (cost_share materials),\\n  labor_share = (cost_share labor),\\n  overhead_share = (cost_share overhead),\\n}\\n\"\n---\nSELECT\n  materials,\n  labor,\n  overhead,\n  cost_total,\n  materials / cost_total AS materials_share,\n  labor / cost_total AS labor_share,\n  overhead / cost_total AS overhead_share\nFROM\n  costs\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__other-examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let is_adult = col -> col >= 18\\nlet writes_code = col -> (col | in [\\\"PRQL\\\", \\\"Rust\\\"])\\nlet square = col -> (col | math.pow 2)\\nlet starts_with_a = col -> (col | text.lower | text.starts_with(\\\"a\\\"))\\n\\nfrom employees\\nselect {\\n    first_name,\\n    last_name,\\n    hobby,\\n    adult = is_adult age,\\n    age_squared = square age,\\n}\\nfilter ((starts_with_a last_name) && (writes_code hobby))\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    first_name,\n    last_name,\n    hobby,\n    age >= 18 AS adult,\n    POW(age, 2) AS age_squared\n  FROM\n    employees\n)\nSELECT\n  first_name,\n  last_name,\n  hobby,\n  adult,\n  age_squared\nFROM\n  table_0\nWHERE\n  LOWER(last_name) LIKE CONCAT('a', '%')\n  AND hobby IN ('PRQL', 'Rust')\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__partial-application__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let top_n = n -> take n\\n\\nfrom invoices\\ntop_n 10\\n\"\n---\nSELECT\n  *\nFROM\n  invoices\nLIMIT\n  10\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__partial-application__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let top_n = n -> take n\\nlet add_constant = x -> derive {constant = x}\\n\\nlet my_pipeline = (top_n 5 | add_constant 42)\\n\\nfrom invoices\\nmy_pipeline\\n\"\n---\nSELECT\n  *,\n  42 AS constant\nFROM\n  invoices\nLIMIT\n  5\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__partial-application__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let top_n = n -> take n\\nlet top_5 = top_n 5\\n\\nfrom invoices\\ntop_5\\n\"\n---\nSELECT\n  *\nFROM\n  invoices\nLIMIT\n  5\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__piping-values-into-functions__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let interp = low:0 high x -> (x - low) / (high - low)\\n\\nfrom students\\nderive {\\n  sat_proportion_1 = (sat_score | interp 1600),\\n  sat_proportion_2 = (sat_score | interp low:0 1600),\\n}\\n\"\n---\nSELECT\n  *,\n  (sat_score - 0) / (1600 - 0) AS sat_proportion_1,\n  (sat_score - 0) / (1600 - 0) AS sat_proportion_2\nFROM\n  students\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__piping-values-into-functions__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let fahrenheit_to_celsius = temp -> (temp - 32) / 1.8\\n\\nfrom cities\\nderive temp_c = (temp_f | fahrenheit_to_celsius)\\n\"\n---\nSELECT\n  *,\n  (temp_f - 32) / 1.8 AS temp_c\nFROM\n  cities\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__functions__piping-values-into-functions__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let fahrenheit_to_celsius = temp -> (temp - 32) / 1.8\\nlet interp = low:0 high x -> (x - low) / (high - low)\\n\\nfrom kettles\\nderive boiling_proportion = (temp_c | fahrenheit_to_celsius | interp 100)\\n\"\n---\nSELECT\n  *,\n  ((temp_c - 32) / 1.8 - 0) / (100 - 0) AS boiling_proportion\nFROM\n  kettles\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__variables__variables--__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let top_50 = (\\n  from employees\\n  sort salary\\n  take 50\\n  aggregate {total_salary = sum salary}\\n)\\n\\nfrom top_50      # Starts a new pipeline\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    salary\n  FROM\n    employees\n  ORDER BY\n    salary\n  LIMIT\n    50\n), top_50 AS (\n  SELECT\n    COALESCE(SUM(salary), 0) AS total_salary\n  FROM\n    table_0\n)\nSELECT\n  total_salary\nFROM\n  top_50\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__variables__variables--__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ntake 50\\ninto first_50\\n\\nfrom first_50\\n\"\n---\nWITH first_50 AS (\n  SELECT\n    *\n  FROM\n    employees\n  LIMIT\n    50\n)\nSELECT\n  *\nFROM\n  first_50\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__declarations__variables__variables--__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let grouping = s\\\"\\\"\\\"\\n  SELECT SUM(a)\\n  FROM tbl\\n  GROUP BY\\n    GROUPING SETS\\n    ((b, c, d), (d), (b, d))\\n\\\"\\\"\\\"\\n\\nfrom grouping\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    SUM(a)\n  FROM\n    tbl\n  GROUP BY\n    GROUPING SETS ((b, c, d), (d), (b, d))\n)\nSELECT\n  *\nFROM\n  table_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__spec__name-resolution__translating-to-sql__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect first_name\\n\"\n---\nSELECT\n  first_name\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__spec__name-resolution__translating-to-sql__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive {first_name, dept_id}\\njoin d=departments (==dept_id)\\nselect {first_name, d.title}\\n\"\n---\nSELECT\n  employees.first_name,\n  d.title\nFROM\n  employees\n  INNER JOIN departments AS d ON employees.dept_id = d.dept_id\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__spec__null__null-handling__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nfilter first_name == null\\nfilter null != last_name\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nWHERE\n  first_name IS NULL\n  AND last_name IS NOT NULL\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__README__standard-library__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive {\\n  gross_salary = (salary + payroll_tax | as int),\\n  gross_salary_rounded = (gross_salary | math.round 0),\\n  time = s\\\"NOW()\\\",  # an s-string, given no `now` function exists in PRQL\\n}\\n\"\n---\nSELECT\n  *,\n  CAST(salary + payroll_tax AS int) AS gross_salary,\n  ROUND(CAST(salary + payroll_tax AS int), 0) AS gross_salary_rounded,\n  NOW() AS time\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__README__standard-library__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.sqlite\\n\\nfrom [{x = 13, y = 5}]\\nselect {\\n  quotient = x / y,\\n  int_quotient = x // y,\\n}\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    13 AS x,\n    5 AS y\n)\nSELECT\n  (x * 1.0 / y) AS quotient,\n  ROUND(ABS(x / y) - 0.5) * SIGN(x) * SIGN(y) AS int_quotient\nFROM\n  table_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__README__standard-library__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.mysql\\n\\nfrom [{x = 13, y = 5}]\\nselect {\\n  quotient = x / y,\\n  int_quotient = x // y,\\n}\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    13 AS x,\n    5 AS y\n)\nSELECT\n  (x / y) AS quotient,\n  (x DIV y) AS int_quotient\nFROM\n  table_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__date__date-functions__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.duckdb\\n\\nfrom invoices\\nselect (invoice_date | date.to_text \\\"%d/%m/%Y\\\")\\n\\n\"\n---\nSELECT\n  strftime(invoice_date, '%d/%m/%Y')\nFROM\n  invoices\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__date__date-functions__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.postgres\\n\\nfrom invoices\\nselect (invoice_date | date.to_text \\\"%d/%m/%Y\\\")\\n\\n\"\n---\nSELECT\n  TO_CHAR(invoice_date, 'DD/MM/YYYY')\nFROM\n  invoices\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__date__date-functions__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.mysql\\n\\nfrom invoices\\nselect (invoice_date | date.to_text \\\"%d/%m/%Y\\\")\\n\\n\"\n---\nSELECT\n  DATE_FORMAT(invoice_date, '%d/%m/%Y')\nFROM\n  invoices\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__distinct__how-do-i-remove-duplicates__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect department\\ngroup employees.* (\\n  take 1\\n)\\n\"\n---\nSELECT\n  DISTINCT department\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__distinct__how-do-i-remove-duplicates__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ngroup employees.* (take 1)\\n\"\n---\nSELECT\n  DISTINCT *\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__distinct__remove-duplicates-from-each-group__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"# youngest employee from each department\\nfrom employees\\ngroup department (\\n  sort age\\n  take 1\\n)\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    *,\n    ROW_NUMBER() OVER (\n      PARTITION BY department\n      ORDER BY\n        age\n    ) AS _expr_0\n  FROM\n    employees\n)\nSELECT\n  *\nFROM\n  table_0\nWHERE\n  _expr_0 <= 1\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__distinct__remove-duplicates-from-each-group__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ngroup {first_name, last_name} (take 1)\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    *,\n    ROW_NUMBER() OVER (PARTITION BY first_name, last_name) AS _expr_0\n  FROM\n    employees\n)\nSELECT\n  *\nFROM\n  table_0\nWHERE\n  _expr_0 <= 1\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__math__example__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect age_squared = (age | math.pow 2)\\n\"\n---\nSELECT\n  POW(age, 2) AS age_squared\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__text__example__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect {\\n  (last_name | text.lower | text.starts_with(\\\"a\\\")),\\n  (title | text.replace \\\"manager\\\" \\\"chief\\\"),\\n}\\n\"\n---\nSELECT\n  LOWER(last_name) LIKE CONCAT('a', '%'),\n  REPLACE(title, 'manager', 'chief')\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__aggregate__aggregate-is-required__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive {avg_sal = average salary}\\n\"\n---\nSELECT\n  *,\n  AVG(salary) OVER () AS avg_sal\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__aggregate__examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\naggregate {\\n  average salary,\\n  ct = count salary\\n}\\n\"\n---\nSELECT\n  AVG(salary),\n  COUNT(*) AS ct\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__aggregate__examples__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ngroup {title, country} (\\n  aggregate {\\n    average salary,\\n    ct = count salary,\\n  }\\n)\\n\"\n---\nSELECT\n  title,\n  country,\n  AVG(salary),\n  COUNT(*) AS ct\nFROM\n  employees\nGROUP BY\n  title,\n  country\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__append__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees_1\\nappend employees_2\\n\"\n---\nSELECT\n  *\nFROM\n  employees_1\nUNION\nALL\nSELECT\n  *\nFROM\n  employees_2\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__append__intersection__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees_1\\nintersect employees_2\\n\"\n---\nSELECT\n  *\nFROM\n  employees_1 AS t\nINTERSECT\nALL\nSELECT\n  *\nFROM\n  employees_2 AS b\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__append__remove__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees_1\\nremove employees_2\\n\"\n---\nSELECT\n  *\nFROM\n  employees_1 AS t\nEXCEPT\n  ALL\nSELECT\n  *\nFROM\n  employees_2 AS b\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__derive__examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive gross_salary = salary + payroll_tax\\n\"\n---\nSELECT\n  *,\n  salary + payroll_tax AS gross_salary\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__derive__examples__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive {\\n  gross_salary = salary + payroll_tax,\\n  gross_cost = gross_salary + benefits_cost\\n}\\n\"\n---\nSELECT\n  *,\n  salary + payroll_tax AS gross_salary,\n  salary + payroll_tax + benefits_cost AS gross_cost\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__filter__examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nfilter age > 25\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nWHERE\n  age > 25\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__filter__examples__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nfilter (age > 25 || department != \\\"IT\\\")\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nWHERE\n  age > 25\n  OR department <> 'IT'\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__filter__examples__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nfilter (department | in [\\\"IT\\\", \\\"HR\\\"])\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nWHERE\n  department IN ('IT', 'HR')\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__filter__examples__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nfilter (age | in 25..40)\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nWHERE\n  age BETWEEN 25 AND 40\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__group__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ngroup {title, country} (\\n  aggregate {\\n    average salary,\\n    ct = count salary\\n  }\\n)\\n\"\n---\nSELECT\n  title,\n  country,\n  AVG(salary),\n  COUNT(*) AS ct\nFROM\n  employees\nGROUP BY\n  title,\n  country\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__group__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nsort join_date\\ntake 1\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nORDER BY\n  join_date\nLIMIT\n  1\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__group__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ngroup role (\\n  sort join_date  # taken from above\\n  take 1\\n)\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    *,\n    ROW_NUMBER() OVER (\n      PARTITION BY role\n      ORDER BY\n        join_date\n    ) AS _expr_0\n  FROM\n    employees\n)\nSELECT\n  *\nFROM\n  table_0\nWHERE\n  _expr_0 <= 1\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__join__examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\njoin side:left positions (employees.id==positions.employee_id)\\n\"\n---\nSELECT\n  employees.*,\n  positions.*\nFROM\n  employees\n  LEFT OUTER JOIN positions ON employees.id = positions.employee_id\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__join__examples__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\njoin side:left p=positions (employees.id==p.employee_id)\\n\"\n---\nSELECT\n  employees.*,\n  p.*\nFROM\n  employees\n  LEFT OUTER JOIN positions AS p ON employees.id = p.employee_id\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__join__examples__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from tracks\\njoin side:left artists (\\n  # This adds a `country` condition, as an alternative to filtering\\n  artists.id==tracks.artist_id && artists.country=='UK'\\n)\\n\"\n---\nSELECT\n  tracks.*,\n  artists.*\nFROM\n  tracks\n  LEFT OUTER JOIN artists ON artists.id = tracks.artist_id\n  AND artists.country = 'UK'\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__join__examples__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from tracks\\njoin side:inner artists (\\n  this.id==that.artist_id\\n)\\n\"\n---\nSELECT\n  tracks.*,\n  artists.*\nFROM\n  tracks\n  INNER JOIN artists ON tracks.id = artists.artist_id\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__join__examples__4.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\njoin positions (==emp_no)\\n\"\n---\nSELECT\n  employees.*,\n  positions.*\nFROM\n  employees\n  INNER JOIN positions ON employees.emp_no = positions.emp_no\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__loop__examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from [{n = 1}]\\nloop (\\n    filter n<4\\n    select n = n+1\\n)\\n\\n# returns [1, 2, 3, 4]\\n\"\n---\nWITH RECURSIVE table_0 AS (\n  SELECT\n    1 AS n\n),\ntable_1 AS (\n  SELECT\n    n\n  FROM\n    table_0\n  UNION\n  ALL\n  SELECT\n    n + 1\n  FROM\n    table_1\n  WHERE\n    n < 4\n)\nSELECT\n  n\nFROM\n  table_1 AS table_2\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__select__examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect name = f\\\"{first_name} {last_name}\\\"\\n\"\n---\nSELECT\n  CONCAT(first_name, ' ', last_name) AS name\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__select__examples__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect {\\n  name = f\\\"{first_name} {last_name}\\\",\\n  age_eoy = dob - @2022-12-31,\\n}\\n\"\n---\nSELECT\n  CONCAT(first_name, ' ', last_name) AS name,\n  dob - DATE '2022-12-31' AS age_eoy\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__select__examples__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect first_name\\n\"\n---\nSELECT\n  first_name\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__select__examples__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from e=employees\\nselect {e.first_name, e.last_name}\\n\"\n---\nSELECT\n  first_name,\n  last_name\nFROM\n  employees AS e\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__select__excluding-columns__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.bigquery\\nfrom tracks\\nselect !{milliseconds, bytes}\\n\"\n---\nSELECT\n  * EXCEPT (milliseconds, bytes)\nFROM\n  tracks\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__select__excluding-columns__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from tracks\\nselect {track_id, title, composer, bytes}\\nselect !{title, composer}\\n\"\n---\nSELECT\n  track_id,\n  bytes\nFROM\n  tracks\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__select__excluding-columns__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from artists\\nderive nick = name\\nselect !{artists.*}\\n\"\n---\nSELECT\n  name AS nick\nFROM\n  artists\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__select__excluding-columns__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.bigquery\\nfrom tracks\\nselect !is_compilation\\n\"\n---\nSELECT\n  NOT is_compilation\nFROM\n  tracks\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__sort__examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nsort age\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nORDER BY\n  age\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__sort__examples__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nsort {-age}\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nORDER BY\n  age DESC\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__sort__examples__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nsort {age, -tenure, +salary}\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nORDER BY\n  age,\n  tenure DESC,\n  salary\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__sort__examples__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nsort {s\\\"substr({first_name}, 2, 5)\\\"}\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    *,\n    substr(first_name, 2, 5) AS _expr_0\n  FROM\n    employees\n)\nSELECT\n  *\nFROM\n  table_0\nORDER BY\n  _expr_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__sort__ordering-guarantees__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nsort tenure\\njoin locations (==employee_id)\\n\"\n---\nSELECT\n  employees.*,\n  locations.*\nFROM\n  employees\n  INNER JOIN locations ON employees.employee_id = locations.employee_id\nORDER BY\n  employees.tenure\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__take__examples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ntake 10\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nLIMIT\n  10\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__take__examples__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from orders\\nsort {-value, created_at}\\ntake 101..110\\n\"\n---\nSELECT\n  *\nFROM\n  orders\nORDER BY\n  value DESC,\n  created_at\nLIMIT\n  10 OFFSET 100\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__window__example__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ngroup employee_id (\\n  sort month\\n  window rolling:12 (\\n    derive {trail_12_m_comp = sum paycheck}\\n  )\\n)\\n\"\n---\nSELECT\n  *,\n  SUM(paycheck) OVER (\n    PARTITION BY employee_id\n    ORDER BY\n      month ROWS BETWEEN 11 PRECEDING AND CURRENT ROW\n  ) AS trail_12_m_comp\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__window__example__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from orders\\nsort day\\nwindow rows:-3..3 (\\n  derive {centered_weekly_average = average value}\\n)\\ngroup {order_month} (\\n  sort day\\n  window expanding:true (\\n    derive {monthly_running_total = sum value}\\n  )\\n)\\n\"\n---\nSELECT\n  *,\n  AVG(value) OVER (\n    ORDER BY\n      day ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING\n  ) AS centered_weekly_average,\n  SUM(value) OVER (\n    PARTITION BY order_month\n    ORDER BY\n      day ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n  ) AS monthly_running_total\nFROM\n  orders\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__window__example__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from [\\n  {time_id=1, value=15},\\n  {time_id=2, value=11},\\n  {time_id=3, value=16},\\n  {time_id=4, value=9},\\n  {time_id=7, value=20},\\n  {time_id=8, value=22},\\n]\\nwindow rows:-2..0 (\\n  sort time_id\\n  derive {sma3rows = average value}\\n)\\nwindow range:-2..0 (\\n  sort time_id\\n  derive {sma3range = average value}\\n)\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    1 AS time_id,\n    15 AS value\n  UNION\n  ALL\n  SELECT\n    2 AS time_id,\n    11 AS value\n  UNION\n  ALL\n  SELECT\n    3 AS time_id,\n    16 AS value\n  UNION\n  ALL\n  SELECT\n    4 AS time_id,\n    9 AS value\n  UNION\n  ALL\n  SELECT\n    7 AS time_id,\n    20 AS value\n  UNION\n  ALL\n  SELECT\n    8 AS time_id,\n    22 AS value\n)\nSELECT\n  time_id,\n  value,\n  AVG(value) OVER (\n    ORDER BY\n      time_id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW\n  ) AS sma3rows,\n  AVG(value) OVER (\n    ORDER BY\n      time_id RANGE BETWEEN 2 PRECEDING AND CURRENT ROW\n  ) AS sma3range\nFROM\n  table_0\nORDER BY\n  time_id\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__window__window-functions-as-first-class-citizens__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nfilter salary < (average salary)\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    *,\n    AVG(salary) OVER () AS _expr_0\n  FROM\n    employees\n)\nSELECT\n  *\nFROM\n  table_0\nWHERE\n  salary < _expr_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__window__windowing-by-default__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nsort age\\nderive {rnk = rank age}\\n\"\n---\nSELECT\n  *,\n  RANK() OVER (\n    ORDER BY\n      age\n  ) AS rnk\nFROM\n  employees\nORDER BY\n  age\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__stdlib__transforms__window__windowing-by-default__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ngroup department (\\n  sort age\\n  derive {rnk = rank age}\\n)\\n\"\n---\nSELECT\n  *,\n  RANK() OVER (\n    PARTITION BY department\n    ORDER BY\n      age\n  ) AS rnk\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__case__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive distance = case [\\n  city == \\\"Calgary\\\" => 0,\\n  city == \\\"Edmonton\\\" => 300,\\n]\\n\"\n---\nSELECT\n  *,\n  CASE\n    WHEN city = 'Calgary' THEN 0\n    WHEN city = 'Edmonton' THEN 300\n    ELSE NULL\n  END AS distance\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__case__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive distance = case [\\n  city == \\\"Calgary\\\" => 0,\\n  city == \\\"Edmonton\\\" => 300,\\n  true => \\\"Unknown\\\",\\n]\\n\"\n---\nSELECT\n  *,\n  CASE\n    WHEN city = 'Calgary' THEN 0\n    WHEN city = 'Edmonton' THEN 300\n    ELSE 'Unknown'\n  END AS distance\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__comments__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees  # Comment 1\\n# Comment 2\\naggregate {average salary}\\n\"\n---\nSELECT\n  AVG(salary)\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__f-strings__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect full_name = f\\\"{first_name} {last_name}\\\"\\n\"\n---\nSELECT\n  CONCAT(first_name, ' ', last_name) AS full_name\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__f-strings__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from web\\nselect url = f\\\"http{tls}://www.{domain}.{tld}/{page}\\\"\\n\"\n---\nSELECT\n  CONCAT(\n    'http',\n    tls,\n    '://www.',\n    domain,\n    '.',\n    tld,\n    '/',\n    page\n  ) AS url\nFROM\n  web\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__f-strings__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from tracks\\nselect length_str = f\\\"{length_seconds / 60} minutes\\\"\\n\"\n---\nError:\n   ╭─[ :2:38 ]\n   │\n 2 │ select length_str = f\"{length_seconds / 60} minutes\"\n   │                                      ┬\n   │                                      ╰── expected something else, '.', ':', or '}', but found \" \"\n───╯\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__keywords__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"std.from my_table\\nstd.select {from = my_table.a, take = my_table.b}\\nstd.take 3\\n\"\n---\nSELECT\n  a AS \"from\",\n  b AS take\nFROM\n  my_table\nLIMIT\n  3\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__keywords__identifiers--keywords__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from invoices\\naggregate (\\n    count this\\n)\\n\"\n---\nSELECT\n  COUNT(*)\nFROM\n  invoices\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__keywords__identifiers--keywords__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from invoices\\njoin tracks (this.track_id==that.id)\\n\"\n---\nSELECT\n  invoices.*,\n  tracks.*\nFROM\n  invoices\n  INNER JOIN tracks ON invoices.track_id = tracks.id\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__keywords__identifiers--keywords__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from invoices\\nderive t = time\\n\"\n---\nError:\n   ╭─[ :2:12 ]\n   │\n 2 │ derive t = time\n   │            ──┬─\n   │              ╰─── expected a value, but found a type\n───╯\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__keywords__identifiers--keywords__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from invoices\\nderive t = this.time\\n\"\n---\nSELECT\n  *,\n  time AS t\nFROM\n  invoices\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__keywords__quoting__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.mysql\\nfrom employees\\nselect `first name`\\n\"\n---\nSELECT\n  `first name`\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__keywords__quoting__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.postgres\\nfrom employees\\nselect `first name`\\n\"\n---\nSELECT\n  \"first name\"\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__keywords__quoting__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.bigquery\\n\\nfrom `project-foo.dataset.table`\\njoin `project-bar.dataset.table` (==col_bax)\\n\"\n---\nSELECT\n  `project-foo.dataset.table`.*,\n  `project-bar.dataset.table`.*\nFROM\n  `project-foo.dataset.table`\n  INNER JOIN `project-bar.dataset.table` ON `project-foo.dataset.table`.col_bax = `project-bar.dataset.table`.col_bax\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__keywords__schemas--database-names__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from my_database.chinook.albums\\n\"\n---\nSELECT\n  *\nFROM\n  my_database.chinook.albums\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__literals__dates__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive age_at_year_end = (@2022-12-31 - dob)\\n\"\n---\nSELECT\n  *,\n  DATE '2022-12-31' - dob AS age_at_year_end\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__literals__durations__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from projects\\nderive first_check_in = start + 10days\\n\"\n---\nSELECT\n  *,\n  \"start\" + INTERVAL 10 DAY AS first_check_in\nFROM\n  projects\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__literals__numbers__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from numbers\\nselect {\\n    small = 1.000_000_1,\\n    big = 5_000_000,\\n    huge = 5e9,\\n    binary = 0b0011,\\n    hex = 0x80,\\n    octal = 0o777,\\n}\\n\"\n---\nSELECT\n  1.0000001 AS small,\n  5000000 AS big,\n  5000000000.0 AS huge,\n  3 AS \"binary\",\n  128 AS hex,\n  511 AS octal\nFROM\n  numbers\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__literals__times__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from orders\\nderive should_have_shipped_today = (order_time < @08:30)\\n\"\n---\nSELECT\n  *,\n  order_time < TIME '08:30' AS should_have_shipped_today\nFROM\n  orders\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__literals__timestamps__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from commits\\nderive first_prql_commit = @2020-01-01T13:19:55-08:00\\nderive first_prql_commit_utc = @2020-01-02T21:19:55Z\\n\"\n---\nSELECT\n  *,\n  TIMESTAMP '2020-01-01T13:19:55-0800' AS first_prql_commit,\n  TIMESTAMP '2020-01-02T21:19:55Z' AS first_prql_commit_utc\nFROM\n  commits\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from foo\\nselect {\\n  circumference = diameter * 3.14159,\\n  area = (diameter / 2) ** 2,\\n  color,\\n}\\nfilter circumference > 10 && color != \\\"red\\\"\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    diameter * 3.14159 AS circumference,\n    POW(diameter / 2, 2) AS area,\n    color\n  FROM\n    foo\n)\nSELECT\n  circumference,\n  area,\n  color\nFROM\n  table_0\nWHERE\n  circumference > 10\n  AND color <> 'red'\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__coalesce__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from orders\\nderive amount ?? 0\\n\"\n---\nSELECT\n  *,\n  COALESCE(amount, 0)\nFROM\n  orders\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__division-and-integer-division__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.sqlite\\n\\nfrom [\\n  {a = 5, b = 2},\\n  {a = 5, b = -2},\\n]\\nselect {\\n  div_out = a / b,\\n  int_div_out = a // b,\\n}\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    5 AS a,\n    2 AS b\n  UNION\n  ALL\n  SELECT\n    5 AS a,\n    -2 AS b\n)\nSELECT\n  (a * 1.0 / b) AS div_out,\n  ROUND(ABS(a / b) - 0.5) * SIGN(a) * SIGN(b) AS int_div_out\nFROM\n  table_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__parentheses__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\n# Requires parentheses, because it contains a pipe\\nderive is_proximate = (distance | in 0..20)\\n# Requires parentheses, because it's a function call\\nderive total_distance = (sum distance)\\n# `??` doesn't require parentheses, as it's not a function call\\nderive min_capped_distance = (min distance ?? 5)\\n# No parentheses needed, because no function call\\nderive travel_time = distance / 40\\n# No inner parentheses needed around `1+1` because no function call\\nderive distance_rounded_2_dp = (math.round 1+1 distance)\\nderive {\\n  # Requires parentheses, because it contains a pipe\\n  is_far = (distance | in 100..),\\n  # The left value of the range requires parentheses,\\n  # because of the minus sign\\n  is_negative = (distance | in (-100..0)),\\n  # ...this is equivalent\\n  is_negative = (distance | in (-100)..0),\\n  # _Technically_, this doesn't require parentheses, because it's\\n  # the RHS of an assignment in a tuple\\n  # (this is especially confusing)\\n  average_distance = average distance,\\n}\\n# Requires parentheses because of the minus sign\\nsort (-distance)\\n# A tuple is fine too\\nsort {-distance}\\n\"\n---\nSELECT\n  *,\n  distance BETWEEN 0 AND 20 AS is_proximate,\n  SUM(distance) OVER () AS total_distance,\n  MIN(COALESCE(distance, 5)) OVER () AS min_capped_distance,\n  distance / 40 AS travel_time,\n  ROUND(distance, 1 + 1) AS distance_rounded_2_dp,\n  distance >= 100 AS is_far,\n  distance BETWEEN -100 AND 0,\n  distance BETWEEN -100 AND 0 AS is_negative,\n  AVG(distance) OVER () AS average_distance\nFROM\n  employees\nORDER BY\n  distance DESC\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__parentheses__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive total_distance = sum distance\\n\"\n---\nError:\n   ╭─[ :2:29 ]\n   │\n 2 │ derive total_distance = sum distance\n   │                             ────┬───\n   │                                 ╰───── Unknown name `distance`\n───╯\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__parentheses__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive other_distance = (sum distance)\\n\"\n---\nSELECT\n  *,\n  SUM(distance) OVER () AS other_distance\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__regex-expressions__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from tracks\\nfilter (name ~= \\\"Love\\\")\\n\"\n---\nSELECT\n  *\nFROM\n  tracks\nWHERE\n  REGEXP(name, 'Love')\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__regex-expressions__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.duckdb\\n\\nfrom artists\\nfilter (name ~= \\\"Love.*You\\\")\\n\"\n---\nSELECT\n  *\nFROM\n  artists\nWHERE\n  REGEXP_MATCHES(name, 'Love.*You')\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__regex-expressions__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.bigquery\\n\\nfrom tracks\\nfilter (name ~= \\\"\\\\\\\\bLove\\\\\\\\b\\\")\\n\"\n---\nSELECT\n  *\nFROM\n  tracks\nWHERE\n  REGEXP_CONTAINS(name, '\\bLove\\b')\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__regex-expressions__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.postgres\\n\\nfrom tracks\\nfilter (name ~= \\\"\\\\\\\\(I Can't Help\\\\\\\\) Falling\\\")\\n\"\n---\nSELECT\n  *\nFROM\n  tracks\nWHERE\n  name ~ '\\(I Can''t Help\\) Falling'\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__regex-expressions__4.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.mysql\\n\\nfrom tracks\\nfilter (name ~= \\\"With You\\\")\\n\"\n---\nSELECT\n  *\nFROM\n  tracks\nWHERE\n  REGEXP_LIKE(name, 'With You', 'c')\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__regex-expressions__5.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"prql target:sql.sqlite\\n\\nfrom tracks\\nfilter (name ~= \\\"But Why Isn't Your Syntax More Similar\\\\\\\\?\\\")\\n\"\n---\nSELECT\n  *\nFROM\n  tracks\nWHERE\n  name REGEXP 'But Why Isn''t Your Syntax More Similar\\?'\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__wrapping-lines__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from artists\\nselect is_europe =\\n\\\\ country == \\\"DE\\\"\\n\\\\ || country == \\\"FR\\\"\\n\\\\ || country == \\\"ES\\\"\\n\"\n---\nSELECT\n  country = 'DE'\n  OR country = 'FR'\n  OR country = 'ES' AS is_europe\nFROM\n  artists\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__operators__wrapping-lines__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from tracks\\n# This would be a really long line without being able to split it:\\nselect listening_time_years = (spotify_plays + apple_music_plays + pandora_plays)\\n# We can toggle between lines when developing:\\n# \\\\ * length_seconds\\n\\\\ * length_s\\n#   min  hour day  year\\n\\\\ / 60 / 60 / 24 / 365\\n\"\n---\nSELECT\n  (\n    spotify_plays + apple_music_plays + pandora_plays\n  ) * length_s / 60 / 60 / 24 / 365 AS listening_time_years\nFROM\n  tracks\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__parameters__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nfilter id == $1\\n\"\n---\nSELECT\n  *\nFROM\n  employees\nWHERE\n  id = $1\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__pipes__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nfilter department == \\\"Product\\\"\\nselect {first_name, last_name}\\n\"\n---\nSELECT\n  first_name,\n  last_name\nFROM\n  employees\nWHERE\n  department = 'Product'\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__pipes__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees | filter department == \\\"Product\\\" | select {first_name, last_name}\\n\"\n---\nSELECT\n  first_name,\n  last_name\nFROM\n  employees\nWHERE\n  department = 'Product'\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__pipes__ceci-nest-pas-une-pipe__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"[\\n  {a=2}      # No pipe from line break before & after this list item\\n]\\nderive {\\n  c = 2 * a, # No pipe from line break before & after this tuple item\\n}\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    2 AS a\n)\nSELECT\n  a,\n  2 * a AS c\nFROM\n  table_0\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__pipes__ceci-nest-pas-une-pipe__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"let b =\\n  \\\\ 3        # No pipe from line break within this line wrap\\n\\n# No pipe from line break before this `from` statement\\n\\nfrom y\\nderive a = b\\n\"\n---\nSELECT\n  *,\n  3 AS a\nFROM\n  y\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__pipes__inner-transforms__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\ngroup {title, country} (\\n  aggregate {\\n    average salary,\\n    ct = count salary,\\n  }\\n)\\n\"\n---\nSELECT\n  title,\n  country,\n  AVG(salary),\n  COUNT(*) AS ct\nFROM\n  employees\nGROUP BY\n  title,\n  country\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__r-strings__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from artists\\nderive normal_string =  \\\"\\\\\\\\\\\\t\\\"   #  two characters - \\\\ and tab (\\\\t)\\nderive raw_string    = r\\\"\\\\\\\\\\\\t\\\"   # four characters - \\\\, \\\\, \\\\, and t\\n\"\n---\nSELECT\n  *,\n  '\\\t' AS normal_string,\n  '\\\\\\t' AS raw_string\nFROM\n  artists\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__ranges__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from events\\nfilter (created_at | in @1776-07-04..@1787-09-17)\\nfilter (magnitude | in 50..100)\\nderive is_northern = (latitude | in 0..)\\n\"\n---\nSELECT\n  *,\n  latitude >= 0 AS is_northern\nFROM\n  events\nWHERE\n  created_at BETWEEN DATE '1776-07-04' AND DATE '1787-09-17'\n  AND magnitude BETWEEN 50 AND 100\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__ranges__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from orders\\nsort {-value, created_at}\\ntake 101..110\\n\"\n---\nSELECT\n  *\nFROM\n  orders\nORDER BY\n  value DESC,\n  created_at\nLIMIT\n  10 OFFSET 100\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__s-strings__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from my_table\\nselect db_version = s\\\"version()\\\"\\n\"\n---\nSELECT\n  version() AS db_version\nFROM\n  my_table\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__s-strings__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\naggregate {average salary}\\n\"\n---\nSELECT\n  AVG(salary)\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__s-strings__2.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from de=dept_emp\\njoin s=salaries side:left (s.emp_no == de.emp_no && s\\\"\\\"\\\"\\n  ({s.from_date}, {s.to_date})\\n  OVERLAPS\\n  ({de.from_date}, {de.to_date})\\n\\\"\\\"\\\")\\n\"\n---\nSELECT\n  de.*,\n  s.*\nFROM\n  dept_emp AS de\n  LEFT OUTER JOIN salaries AS s ON s.emp_no = de.emp_no\n  AND (s.from_date, s.to_date) OVERLAPS (de.from_date, de.to_date)\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__s-strings__3.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from s\\\"SELECT DISTINCT ON first_name, id, age FROM employees ORDER BY age ASC\\\"\\njoin s = s\\\"SELECT * FROM salaries\\\" (==id)\\n\"\n---\nWITH table_0 AS (\n  SELECT\n    DISTINCT ON first_name,\n    id,\n    age\n  FROM\n    employees\n  ORDER BY\n    age ASC\n),\ntable_1 AS (\n  SELECT\n    *\n  FROM\n    salaries\n)\nSELECT\n  table_0.*,\n  table_1.*\nFROM\n  table_0\n  INNER JOIN table_1 ON table_0.id = table_1.id\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__s-strings__braces__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive {\\n  has_valid_title = s\\\"regexp_contains(title, '([a-z0-9]*-){{2,}}')\\\"\\n}\\n\"\n---\nSELECT\n  *,\n  regexp_contains(title, '([a-z0-9]*-){2,}') AS has_valid_title\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__s-strings__precedence-within-s-strings__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive {\\n  gross_salary = salary + benefits,\\n  daily_rate = s\\\"{gross_salary} / 365\\\"\\n}\\n\"\n---\nSELECT\n  *,\n  salary + benefits AS gross_salary,\n  salary + benefits / 365 AS daily_rate\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__s-strings__precedence-within-s-strings__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nderive {\\n  gross_salary = salary + benefits,\\n  daily_rate = s\\\"({gross_salary}) / 365\\\"\\n}\\n\"\n---\nSELECT\n  *,\n  salary + benefits AS gross_salary,\n  (salary + benefits) / 365 AS daily_rate\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__strings__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from artists\\nderive {\\n  single        =   'hello world',\\n  double        =   \\\"hello world\\\",\\n  double_triple = \\\"\\\"\\\"hello world\\\"\\\"\\\",\\n}\\n\"\n---\nSELECT\n  *,\n  'hello world' AS single,\n  'hello world' AS double,\n  'hello world' AS double_triple\nFROM\n  artists\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__strings__quoting-and-escape-characters__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from artists\\nselect {\\n  other   = '\\\"hello world\\\"',\\n  escaped = \\\"\\\\\\\"hello world\\\\\\\"\\\",\\n  triple  = \\\"\\\"\\\"I said \\\"hello world\\\"!\\\"\\\"\\\",\\n}\\n\"\n---\nSELECT\n  '\"hello world\"' AS other,\n  '\"hello world\"' AS escaped,\n  'I said \"hello world\"!' AS triple\nFROM\n  artists\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__strings__quoting-and-escape-characters__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from artists\\nderive escapes = \\\"\\\\tXYZ\\\\n \\\\\\\\ \\\"                            # tab (\\\\t), \\\"XYZ\\\", newline (\\\\n), \\\" \\\", \\\\, \\\" \\\"\\nderive world = \\\"\\\\u{0048}\\\\u{0065}\\\\u{006C}\\\\u{006C}\\\\u{006F}\\\" # \\\"Hello\\\"\\nderive hex = \\\"\\\\x48\\\\x65\\\\x6C\\\\x6C\\\\x6F\\\"                       # \\\"Hello\\\"\\nderive turtle = \\\"\\\\u{01F422}\\\"                              # \\\"🐢\\\"\\n\"\n---\nSELECT\n  *,\n  '\tXYZ\n \\ ' AS escapes,\n  'Hello' AS world,\n  'Hello' AS hex,\n  '🐢' AS turtle\nFROM\n  artists\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__tuples__0.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect {first_name}\\n\"\n---\nSELECT\n  first_name\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/snapshots/documentation__book__reference__syntax__tuples__1.snap",
    "content": "---\nsource: web/book/tests/documentation/book.rs\nexpression: \"from employees\\nselect first_name\\n\"\n---\nSELECT\n  first_name\nFROM\n  employees\n"
  },
  {
    "path": "web/book/tests/documentation/website.rs",
    "content": "use std::fs::read_dir;\n\nuse regex::Regex;\nuse serde_yaml::Value;\nuse similar_asserts::assert_eq;\n\nuse super::compile;\n\nfn sql_normalize(sql: &str) -> String {\n    let re = Regex::new(r\"\\n\\s+\").unwrap();\n    re.replace_all(sql, \" \").trim().to_string()\n}\n\n#[test]\nfn test_website_examples() {\n    for example in read_dir(\"../website/data/examples\").unwrap().flatten() {\n        let file = std::fs::File::open(example.path()).unwrap();\n        let example_value: Value = serde_yaml::from_reader(&file).unwrap();\n        let prql = example_value.get(\"prql\").unwrap().as_str().unwrap();\n\n        let compiled_sql = compile(prql).unwrap();\n\n        if let Some(sql) = example_value.get(\"sql\") {\n            assert_eq!(\n                sql_normalize(&compiled_sql),\n                sql_normalize(sql.as_str().unwrap()),\n                \"Failed for file: {:?}\",\n                example.path()\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "web/book/theme/head.hbs",
    "content": "<!-- Google tag (gtag.js) -->\n<script\n  async\n  src=\"https://www.googletagmanager.com/gtag/js?id=G-LQJDD599T4\"\n></script>\n<script>\n  window.dataLayer = window.dataLayer || []; function gtag() {\n  dataLayer.push(arguments); } gtag('js', new Date()); gtag('config',\n  'G-LQJDD599T4');\n</script>"
  },
  {
    "path": "web/book/theme/highlight.js",
    "content": "var hljs=function(){\"use strict\";var e={exports:{}};function n(e){return e instanceof Map?e.clear=e.delete=e.set=()=>{throw Error(\"map is read-only\")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{throw Error(\"set is read-only\")}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach(t=>{var a=e[t];\"object\"!=typeof a||Object.isFrozen(a)||n(a)}),e}e.exports=n,e.exports.default=n;var t=e.exports;class a{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function i(e){return e.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\").replace(/'/g,\"&#x27;\")}function r(e,...n){const t=Object.create(null);for(const n in e)t[n]=e[n];return n.forEach(e=>{for(const n in e)t[n]=e[n]}),t}const s=e=>!!e.kind;class o{constructor(e,n){this.buffer=\"\",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=i(e)}openNode(e){if(!s(e))return;let n=e.kind;n=e.sublanguage?\"language-\"+n:((e,{prefix:n})=>{if(e.includes(\".\")){const t=e.split(\".\");return[`${n}${t.shift()}`,...t.map((e,n)=>`${e}${\"_\".repeat(n+1)}`)].join(\" \")}return`${n}${e}`})(n,{prefix:this.classPrefix}),this.span(n)}closeNode(e){s(e)&&(this.buffer+=\"</span>\")}value(){return this.buffer}span(e){this.buffer+=`<span class=\"${e}\">`}}class l{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return\"string\"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){\"string\"!=typeof e&&e.children&&(e.children.every(e=>\"string\"==typeof e)?e.children=[e.children.join(\"\")]:e.children.forEach(e=>{l._collapse(e)}))}}class c extends l{constructor(e){super(),this.options=e}addKeyword(e,n){\"\"!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){\"\"!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new o(this,this.options).value()}finalize(){return!0}}function d(e){return e?\"string\"==typeof e?e:e.source:null}function g(e){return m(\"(?=\",e,\")\")}function u(e){return m(\"(?:\",e,\")*\")}function b(e){return m(\"(?:\",e,\")?\")}function m(...e){return e.map(e=>d(e)).join(\"\")}function p(...e){const n=(e=>{const n=e[e.length-1];return\"object\"==typeof n&&n.constructor===Object?(e.splice(e.length-1,1),n):{}})(e);return\"(\"+(n.capture?\"\":\"?:\")+e.map(e=>d(e)).join(\"|\")+\")\"}function _(e){return RegExp(e.toString()+\"|\").exec(\"\").length-1}const h=/\\[(?:[^\\\\\\]]|\\\\.)*\\]|\\(\\??|\\\\([1-9][0-9]*)|\\\\./;function f(e,{joinWith:n}){let t=0;return e.map(e=>{t+=1;const n=t;let a=d(e),i=\"\";for(;a.length>0;){const e=h.exec(a);if(!e){i+=a;break}i+=a.substring(0,e.index),a=a.substring(e.index+e[0].length),\"\\\\\"===e[0][0]&&e[1]?i+=\"\\\\\"+(Number(e[1])+n):(i+=e[0],\"(\"===e[0]&&t++)}return i}).map(e=>`(${e})`).join(n)}const E=\"[a-zA-Z]\\\\w*\",y=\"[a-zA-Z_]\\\\w*\",w=\"\\\\b\\\\d+(\\\\.\\\\d+)?\",N=\"(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)([eE][-+]?\\\\d+)?)\",v=\"\\\\b(0b[01]+)\",k={begin:\"\\\\\\\\[\\\\s\\\\S]\",relevance:0},O={scope:\"string\",begin:\"'\",end:\"'\",illegal:\"\\\\n\",contains:[k]},x={scope:\"string\",begin:'\"',end:'\"',illegal:\"\\\\n\",contains:[k]},M=(e,n,t={})=>{const a=r({scope:\"comment\",begin:e,end:n,contains:[]},t);a.contains.push({scope:\"doctag\",begin:\"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)\",end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});const i=p(\"I\",\"a\",\"is\",\"so\",\"us\",\"to\",\"at\",\"if\",\"in\",\"it\",\"on\",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return a.contains.push({begin:m(/[ ]+/,\"(\",i,/[.]?[:]?([.][ ]|[ ])/,\"){3}\")}),a},S=M(\"//\",\"$\"),A=M(\"/\\\\*\",\"\\\\*/\"),C=M(\"#\",\"$\");var T=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\\b\\B/,IDENT_RE:E,UNDERSCORE_IDENT_RE:y,NUMBER_RE:w,C_NUMBER_RE:N,BINARY_NUMBER_RE:v,RE_STARTERS_RE:\"!|!=|!==|%|%=|&|&&|&=|\\\\*|\\\\*=|\\\\+|\\\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\\\?|\\\\[|\\\\{|\\\\(|\\\\^|\\\\^=|\\\\||\\\\|=|\\\\|\\\\||~\",SHEBANG:(e={})=>{const n=/^#![ ]*\\//;return e.binary&&(e.begin=m(n,/.*\\b/,e.binary,/\\b.*/)),r({scope:\"meta\",begin:n,end:/$/,relevance:0,\"on:begin\":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:k,APOS_STRING_MODE:O,QUOTE_STRING_MODE:x,PHRASAL_WORDS_MODE:{begin:/\\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\\b/},COMMENT:M,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:A,HASH_COMMENT_MODE:C,NUMBER_MODE:{scope:\"number\",begin:w,relevance:0},C_NUMBER_MODE:{scope:\"number\",begin:N,relevance:0},BINARY_NUMBER_MODE:{scope:\"number\",begin:v,relevance:0},REGEXP_MODE:{begin:/(?=\\/[^/\\n]*\\/)/,contains:[{scope:\"regexp\",begin:/\\//,end:/\\/[gimuy]*/,illegal:/\\n/,contains:[k,{begin:/\\[/,end:/\\]/,relevance:0,contains:[k]}]}]},TITLE_MODE:{scope:\"title\",begin:E,relevance:0},UNDERSCORE_TITLE_MODE:{scope:\"title\",begin:y,relevance:0},METHOD_GUARD:{begin:\"\\\\.\\\\s*[a-zA-Z_]\\\\w*\",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{\"on:begin\":(e,n)=>{n.data._beginMatch=e[1]},\"on:end\":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})});function R(e,n){\".\"===e.input[e.index-1]&&n.ignoreMatch()}function D(e,n){void 0!==e.className&&(e.scope=e.className,delete e.className)}function I(e,n){n&&e.beginKeywords&&(e.begin=\"\\\\b(\"+e.beginKeywords.split(\" \").join(\"|\")+\")(?!\\\\.)(?=\\\\b|\\\\s)\",e.__beforeBegin=R,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,void 0===e.relevance&&(e.relevance=0))}function L(e,n){Array.isArray(e.illegal)&&(e.illegal=p(...e.illegal))}function B(e,n){if(e.match){if(e.begin||e.end)throw Error(\"begin & end are not supported with match\");e.begin=e.match,delete e.match}}function $(e,n){void 0===e.relevance&&(e.relevance=1)}const z=(e,n)=>{if(!e.beforeMatch)return;if(e.starts)throw Error(\"beforeMatch cannot be used with starts\");const t=Object.assign({},e);Object.keys(e).forEach(n=>{delete e[n]}),e.keywords=t.keywords,e.begin=m(t.beforeMatch,g(t.begin)),e.starts={relevance:0,contains:[Object.assign(t,{endsParent:!0})]},e.relevance=0,delete t.beforeMatch},F=[\"of\",\"and\",\"for\",\"in\",\"not\",\"or\",\"if\",\"then\",\"parent\",\"list\",\"value\"];function U(e,n,t=\"keyword\"){const a=Object.create(null);return\"string\"==typeof e?i(t,e.split(\" \")):Array.isArray(e)?i(t,e):Object.keys(e).forEach(t=>{Object.assign(a,U(e[t],n,t))}),a;function i(e,t){n&&(t=t.map(e=>e.toLowerCase())),t.forEach(n=>{const t=n.split(\"|\");a[t[0]]=[e,j(t[0],t[1])]})}}function j(e,n){return n?Number(n):(e=>F.includes(e.toLowerCase()))(e)?0:1}const P={},K=e=>{console.error(e)},H=(e,...n)=>{console.log(\"WARN: \"+e,...n)},q=(e,n)=>{P[`${e}/${n}`]||(console.log(`Deprecated as of ${e}. ${n}`),P[`${e}/${n}`]=!0)},Z=Error();function G(e,n,{key:t}){let a=0;const i=e[t],r={},s={};for(let e=1;e<=n.length;e++)s[e+a]=i[e],r[e+a]=!0,a+=_(n[e-1]);e[t]=s,e[t]._emit=r,e[t]._multi=!0}function W(e){(e=>{e.scope&&\"object\"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope,delete e.scope)})(e),\"string\"==typeof e.beginScope&&(e.beginScope={_wrap:e.beginScope}),\"string\"==typeof e.endScope&&(e.endScope={_wrap:e.endScope}),(e=>{if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw K(\"skip, excludeBegin, returnBegin not compatible with beginScope: {}\"),Z;if(\"object\"!=typeof e.beginScope||null===e.beginScope)throw K(\"beginScope must be object\"),Z;G(e,e.begin,{key:\"beginScope\"}),e.begin=f(e.begin,{joinWith:\"\"})}})(e),(e=>{if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw K(\"skip, excludeEnd, returnEnd not compatible with endScope: {}\"),Z;if(\"object\"!=typeof e.endScope||null===e.endScope)throw K(\"endScope must be object\"),Z;G(e,e.end,{key:\"endScope\"}),e.end=f(e.end,{joinWith:\"\"})}})(e)}function Q(e){function n(n,t){return RegExp(d(n),\"m\"+(e.case_insensitive?\"i\":\"\")+(e.unicodeRegex?\"u\":\"\")+(t?\"g\":\"\"))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=_(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(f(e,{joinWith:\"|\"}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),a=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,a)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),\"begin\"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;let t=n.exec(e);if(this.resumingScanAtSamePosition())if(t&&t.index===this.lastIndex);else{const n=this.getMatcher(0);n.lastIndex=this.lastIndex+1,t=n.exec(e)}return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&this.considerAll()),t}}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes(\"self\"))throw Error(\"ERR: contains `self` is not supported at the top-level of a language.  See documentation.\");return e.classNameAliases=r(e.classNameAliases||{}),function t(i,s){const o=i;if(i.isCompiled)return o;[D,B,W,z].forEach(e=>e(i,s)),e.compilerExtensions.forEach(e=>e(i,s)),i.__beforeBegin=null,[I,L,$].forEach(e=>e(i,s)),i.isCompiled=!0;let l=null;return\"object\"==typeof i.keywords&&i.keywords.$pattern&&(i.keywords=Object.assign({},i.keywords),l=i.keywords.$pattern,delete i.keywords.$pattern),l=l||/\\w+/,i.keywords&&(i.keywords=U(i.keywords,e.case_insensitive)),o.keywordPatternRe=n(l,!0),s&&(i.begin||(i.begin=/\\B|\\b/),o.beginRe=n(o.begin),i.end||i.endsWithParent||(i.end=/\\B|\\b/),i.end&&(o.endRe=n(o.end)),o.terminatorEnd=d(o.end)||\"\",i.endsWithParent&&s.terminatorEnd&&(o.terminatorEnd+=(i.end?\"|\":\"\")+s.terminatorEnd)),i.illegal&&(o.illegalRe=n(i.illegal)),i.contains||(i.contains=[]),i.contains=[].concat(...i.contains.map(e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map(n=>r(e,{variants:null},n))),e.cachedVariants?e.cachedVariants:X(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e))(\"self\"===e?i:e))),i.contains.forEach(e=>{t(e,o)}),i.starts&&t(i.starts,s),o.matcher=(e=>{const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:\"begin\"})),e.terminatorEnd&&n.addRule(e.terminatorEnd,{type:\"end\"}),e.illegal&&n.addRule(e.illegal,{type:\"illegal\"}),n})(o),o}(e)}function X(e){return!!e&&(e.endsWithParent||X(e.starts))}class V extends Error{constructor(e,n){super(e),this.name=\"HTMLInjectionError\",this.html=n}}const J=i,Y=r,ee=Symbol(\"nomatch\");var ne=(e=>{const n=Object.create(null),i=Object.create(null),r=[];let s=!0;const o=\"Could not find the language '{}', did you forget to load/include a language module?\",l={disableAutodetect:!0,name:\"Plain text\",contains:[]};let d={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\\blang(?:uage)?-([\\w-]+)\\b/i,classPrefix:\"hljs-\",cssSelector:\"pre code\",languages:null,__emitter:c};function _(e){return d.noHighlightRe.test(e)}function h(e,n,t){let a=\"\",i=\"\";\"object\"==typeof n?(a=e,t=n.ignoreIllegals,i=n.language):(q(\"10.7.0\",\"highlight(lang, code, ...args) has been deprecated.\"),q(\"10.7.0\",\"Please use highlight(code, options) instead.\\nhttps://github.com/highlightjs/highlight.js/issues/2277\"),i=e,a=n),void 0===t&&(t=!0);const r={code:a,language:i};x(\"before:highlight\",r);const s=r.result?r.result:f(r.language,r.code,t);return s.code=r.code,x(\"after:highlight\",s),s}function f(e,t,i,r){const l=Object.create(null);function c(){if(!O.keywords)return void M.addText(S);let e=0;O.keywordPatternRe.lastIndex=0;let n=O.keywordPatternRe.exec(S),t=\"\";for(;n;){t+=S.substring(e,n.index);const i=w.case_insensitive?n[0].toLowerCase():n[0],r=(a=i,O.keywords[a]);if(r){const[e,a]=r;if(M.addText(t),t=\"\",l[i]=(l[i]||0)+1,l[i]<=7&&(A+=a),e.startsWith(\"_\"))t+=n[0];else{const t=w.classNameAliases[e]||e;M.addKeyword(n[0],t)}}else t+=n[0];e=O.keywordPatternRe.lastIndex,n=O.keywordPatternRe.exec(S)}var a;t+=S.substr(e),M.addText(t)}function g(){null!=O.subLanguage?(()=>{if(\"\"===S)return;let e=null;if(\"string\"==typeof O.subLanguage){if(!n[O.subLanguage])return void M.addText(S);e=f(O.subLanguage,S,!0,x[O.subLanguage]),x[O.subLanguage]=e._top}else e=E(S,O.subLanguage.length?O.subLanguage:null);O.relevance>0&&(A+=e.relevance),M.addSublanguage(e._emitter,e.language)})():c(),S=\"\"}function u(e,n){let t=1;const a=n.length-1;for(;t<=a;){if(!e._emit[t]){t++;continue}const a=w.classNameAliases[e[t]]||e[t],i=n[t];a?M.addKeyword(i,a):(S=i,c(),S=\"\"),t++}}function b(e,n){return e.scope&&\"string\"==typeof e.scope&&M.openNode(w.classNameAliases[e.scope]||e.scope),e.beginScope&&(e.beginScope._wrap?(M.addKeyword(S,w.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap),S=\"\"):e.beginScope._multi&&(u(e.beginScope,n),S=\"\")),O=Object.create(e,{parent:{value:O}}),O}function m(e,n,t){let i=((e,n)=>{const t=e&&e.exec(n);return t&&0===t.index})(e.endRe,t);if(i){if(e[\"on:end\"]){const t=new a(e);e[\"on:end\"](n,t),t.isMatchIgnored&&(i=!1)}if(i){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return m(e.parent,n,t)}function p(e){return 0===O.matcher.regexIndex?(S+=e[0],1):(R=!0,0)}function _(e){const n=e[0],a=t.substr(e.index),i=m(O,e,a);if(!i)return ee;const r=O;O.endScope&&O.endScope._wrap?(g(),M.addKeyword(n,O.endScope._wrap)):O.endScope&&O.endScope._multi?(g(),u(O.endScope,e)):r.skip?S+=n:(r.returnEnd||r.excludeEnd||(S+=n),g(),r.excludeEnd&&(S=n));do{O.scope&&M.closeNode(),O.skip||O.subLanguage||(A+=O.relevance),O=O.parent}while(O!==i.parent);return i.starts&&b(i.starts,e),r.returnEnd?0:n.length}let h={};function y(n,r){const o=r&&r[0];if(S+=n,null==o)return g(),0;if(\"begin\"===h.type&&\"end\"===r.type&&h.index===r.index&&\"\"===o){if(S+=t.slice(r.index,r.index+1),!s){const n=Error(`0 width match regex (${e})`);throw n.languageName=e,n.badRule=h.rule,n}return 1}if(h=r,\"begin\"===r.type)return(e=>{const n=e[0],t=e.rule,i=new a(t),r=[t.__beforeBegin,t[\"on:begin\"]];for(const t of r)if(t&&(t(e,i),i.isMatchIgnored))return p(n);return t.skip?S+=n:(t.excludeBegin&&(S+=n),g(),t.returnBegin||t.excludeBegin||(S=n)),b(t,e),t.returnBegin?0:n.length})(r);if(\"illegal\"===r.type&&!i){const e=Error('Illegal lexeme \"'+o+'\" for mode \"'+(O.scope||\"<unnamed>\")+'\"');throw e.mode=O,e}if(\"end\"===r.type){const e=_(r);if(e!==ee)return e}if(\"illegal\"===r.type&&\"\"===o)return 1;if(T>1e5&&T>3*r.index)throw Error(\"potential infinite loop, way more iterations than matches\");return S+=o,o.length}const w=v(e);if(!w)throw K(o.replace(\"{}\",e)),Error('Unknown language: \"'+e+'\"');const N=Q(w);let k=\"\",O=r||N;const x={},M=new d.__emitter(d);(()=>{const e=[];for(let n=O;n!==w;n=n.parent)n.scope&&e.unshift(n.scope);e.forEach(e=>M.openNode(e))})();let S=\"\",A=0,C=0,T=0,R=!1;try{for(O.matcher.considerAll();;){T++,R?R=!1:O.matcher.considerAll(),O.matcher.lastIndex=C;const e=O.matcher.exec(t);if(!e)break;const n=y(t.substring(C,e.index),e);C=e.index+n}return y(t.substr(C)),M.closeAllNodes(),M.finalize(),k=M.toHTML(),{language:e,value:k,relevance:A,illegal:!1,_emitter:M,_top:O}}catch(n){if(n.message&&n.message.includes(\"Illegal\"))return{language:e,value:J(t),illegal:!0,relevance:0,_illegalBy:{message:n.message,index:C,context:t.slice(C-100,C+100),mode:n.mode,resultSoFar:k},_emitter:M};if(s)return{language:e,value:J(t),illegal:!1,relevance:0,errorRaised:n,_emitter:M,_top:O};throw n}}function E(e,t){t=t||d.languages||Object.keys(n);const a=(e=>{const n={value:J(e),illegal:!1,relevance:0,_top:l,_emitter:new d.__emitter(d)};return n._emitter.addText(e),n})(e),i=t.filter(v).filter(O).map(n=>f(n,e,!1));i.unshift(a);const r=i.sort((e,n)=>{if(e.relevance!==n.relevance)return n.relevance-e.relevance;if(e.language&&n.language){if(v(e.language).supersetOf===n.language)return 1;if(v(n.language).supersetOf===e.language)return-1}return 0}),[s,o]=r,c=s;return c.secondBest=o,c}function y(e){let n=null;const t=(e=>{let n=e.className+\" \";n+=e.parentNode?e.parentNode.className:\"\";const t=d.languageDetectRe.exec(n);if(t){const n=v(t[1]);return n||(H(o.replace(\"{}\",t[1])),H(\"Falling back to no-highlight mode for this block.\",e)),n?t[1]:\"no-highlight\"}return n.split(/\\s+/).find(e=>_(e)||v(e))})(e);if(_(t))return;if(x(\"before:highlightElement\",{el:e,language:t}),e.children.length>0&&(d.ignoreUnescapedHTML||(console.warn(\"One of your code blocks includes unescaped HTML. This is a potentially serious security risk.\"),console.warn(\"https://github.com/highlightjs/highlight.js/wiki/security\"),console.warn(\"The element with unescaped HTML:\"),console.warn(e)),d.throwUnescapedHTML))throw new V(\"One of your code blocks includes unescaped HTML.\",e.innerHTML);n=e;const a=n.textContent,r=t?h(a,{language:t,ignoreIllegals:!0}):E(a);e.innerHTML=r.value,((e,n,t)=>{const a=n&&i[n]||t;e.classList.add(\"hljs\"),e.classList.add(\"language-\"+a)})(e,t,r.language),e.result={language:r.language,re:r.relevance,relevance:r.relevance},r.secondBest&&(e.secondBest={language:r.secondBest.language,relevance:r.secondBest.relevance}),x(\"after:highlightElement\",{el:e,result:r,text:a})}let w=!1;function N(){\"loading\"!==document.readyState?document.querySelectorAll(d.cssSelector).forEach(y):w=!0}function v(e){return e=(e||\"\").toLowerCase(),n[e]||n[i[e]]}function k(e,{languageName:n}){\"string\"==typeof e&&(e=[e]),e.forEach(e=>{i[e.toLowerCase()]=n})}function O(e){const n=v(e);return n&&!n.disableAutodetect}function x(e,n){const t=e;r.forEach(e=>{e[t]&&e[t](n)})}\"undefined\"!=typeof window&&window.addEventListener&&window.addEventListener(\"DOMContentLoaded\",()=>{w&&N()},!1),Object.assign(e,{highlight:h,highlightAuto:E,highlightAll:N,highlightElement:y,highlightBlock:e=>(q(\"10.7.0\",\"highlightBlock will be removed entirely in v12.0\"),q(\"10.7.0\",\"Please use highlightElement now.\"),y(e)),configure:e=>{d=Y(d,e)},initHighlighting:()=>{N(),q(\"10.6.0\",\"initHighlighting() deprecated.  Use highlightAll() now.\")},initHighlightingOnLoad:()=>{N(),q(\"10.6.0\",\"initHighlightingOnLoad() deprecated.  Use highlightAll() now.\")},registerLanguage:(t,a)=>{let i=null;try{i=a(e)}catch(e){if(K(\"Language definition for '{}' could not be registered.\".replace(\"{}\",t)),!s)throw e;K(e),i=l}i.name||(i.name=t),n[t]=i,i.rawDefinition=a.bind(null,e),i.aliases&&k(i.aliases,{languageName:t})},unregisterLanguage:e=>{delete n[e];for(const n of Object.keys(i))i[n]===e&&delete i[n]},listLanguages:()=>Object.keys(n),getLanguage:v,registerAliases:k,autoDetection:O,inherit:Y,addPlugin:e=>{(e=>{e[\"before:highlightBlock\"]&&!e[\"before:highlightElement\"]&&(e[\"before:highlightElement\"]=n=>{e[\"before:highlightBlock\"](Object.assign({block:n.el},n))}),e[\"after:highlightBlock\"]&&!e[\"after:highlightElement\"]&&(e[\"after:highlightElement\"]=n=>{e[\"after:highlightBlock\"](Object.assign({block:n.el},n))})})(e),r.push(e)}}),e.debugMode=()=>{s=!1},e.safeMode=()=>{s=!0},e.versionString=\"11.5.0\",e.regex={concat:m,lookahead:g,either:p,optional:b,anyNumberOfTimes:u};for(const e in T)\"object\"==typeof T[e]&&t(T[e]);return Object.assign(e,T),e})({});const te=e=>({IMPORTANT:{scope:\"meta\",begin:\"!important\"},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:\"number\",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\\b/},FUNCTION_DISPATCH:{className:\"built_in\",begin:/[\\w-]+(?=\\()/},ATTRIBUTE_SELECTOR_MODE:{scope:\"selector-attr\",begin:/\\[/,end:/\\]/,illegal:\"$\",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:\"number\",begin:e.NUMBER_RE+\"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?\",relevance:0},CSS_VARIABLE:{className:\"attr\",begin:/--[A-Za-z][A-Za-z0-9_-]*/}}),ae=[\"a\",\"abbr\",\"address\",\"article\",\"aside\",\"audio\",\"b\",\"blockquote\",\"body\",\"button\",\"canvas\",\"caption\",\"cite\",\"code\",\"dd\",\"del\",\"details\",\"dfn\",\"div\",\"dl\",\"dt\",\"em\",\"fieldset\",\"figcaption\",\"figure\",\"footer\",\"form\",\"h1\",\"h2\",\"h3\",\"h4\",\"h5\",\"h6\",\"header\",\"hgroup\",\"html\",\"i\",\"iframe\",\"img\",\"input\",\"ins\",\"kbd\",\"label\",\"legend\",\"li\",\"main\",\"mark\",\"menu\",\"nav\",\"object\",\"ol\",\"p\",\"q\",\"quote\",\"samp\",\"section\",\"span\",\"strong\",\"summary\",\"sup\",\"table\",\"tbody\",\"td\",\"textarea\",\"tfoot\",\"th\",\"thead\",\"time\",\"tr\",\"ul\",\"var\",\"video\"],ie=[\"any-hover\",\"any-pointer\",\"aspect-ratio\",\"color\",\"color-gamut\",\"color-index\",\"device-aspect-ratio\",\"device-height\",\"device-width\",\"display-mode\",\"forced-colors\",\"grid\",\"height\",\"hover\",\"inverted-colors\",\"monochrome\",\"orientation\",\"overflow-block\",\"overflow-inline\",\"pointer\",\"prefers-color-scheme\",\"prefers-contrast\",\"prefers-reduced-motion\",\"prefers-reduced-transparency\",\"resolution\",\"scan\",\"scripting\",\"update\",\"width\",\"min-width\",\"max-width\",\"min-height\",\"max-height\"],re=[\"active\",\"any-link\",\"blank\",\"checked\",\"current\",\"default\",\"defined\",\"dir\",\"disabled\",\"drop\",\"empty\",\"enabled\",\"first\",\"first-child\",\"first-of-type\",\"fullscreen\",\"future\",\"focus\",\"focus-visible\",\"focus-within\",\"has\",\"host\",\"host-context\",\"hover\",\"indeterminate\",\"in-range\",\"invalid\",\"is\",\"lang\",\"last-child\",\"last-of-type\",\"left\",\"link\",\"local-link\",\"not\",\"nth-child\",\"nth-col\",\"nth-last-child\",\"nth-last-col\",\"nth-last-of-type\",\"nth-of-type\",\"only-child\",\"only-of-type\",\"optional\",\"out-of-range\",\"past\",\"placeholder-shown\",\"read-only\",\"read-write\",\"required\",\"right\",\"root\",\"scope\",\"target\",\"target-within\",\"user-invalid\",\"valid\",\"visited\",\"where\"],se=[\"after\",\"backdrop\",\"before\",\"cue\",\"cue-region\",\"first-letter\",\"first-line\",\"grammar-error\",\"marker\",\"part\",\"placeholder\",\"selection\",\"slotted\",\"spelling-error\"],oe=[\"align-content\",\"align-items\",\"align-self\",\"all\",\"animation\",\"animation-delay\",\"animation-direction\",\"animation-duration\",\"animation-fill-mode\",\"animation-iteration-count\",\"animation-name\",\"animation-play-state\",\"animation-timing-function\",\"backface-visibility\",\"background\",\"background-attachment\",\"background-blend-mode\",\"background-clip\",\"background-color\",\"background-image\",\"background-origin\",\"background-position\",\"background-repeat\",\"background-size\",\"block-size\",\"border\",\"border-block\",\"border-block-color\",\"border-block-end\",\"border-block-end-color\",\"border-block-end-style\",\"border-block-end-width\",\"border-block-start\",\"border-block-start-color\",\"border-block-start-style\",\"border-block-start-width\",\"border-block-style\",\"border-block-width\",\"border-bottom\",\"border-bottom-color\",\"border-bottom-left-radius\",\"border-bottom-right-radius\",\"border-bottom-style\",\"border-bottom-width\",\"border-collapse\",\"border-color\",\"border-image\",\"border-image-outset\",\"border-image-repeat\",\"border-image-slice\",\"border-image-source\",\"border-image-width\",\"border-inline\",\"border-inline-color\",\"border-inline-end\",\"border-inline-end-color\",\"border-inline-end-style\",\"border-inline-end-width\",\"border-inline-start\",\"border-inline-start-color\",\"border-inline-start-style\",\"border-inline-start-width\",\"border-inline-style\",\"border-inline-width\",\"border-left\",\"border-left-color\",\"border-left-style\",\"border-left-width\",\"border-radius\",\"border-right\",\"border-right-color\",\"border-right-style\",\"border-right-width\",\"border-spacing\",\"border-style\",\"border-top\",\"border-top-color\",\"border-top-left-radius\",\"border-top-right-radius\",\"border-top-style\",\"border-top-width\",\"border-width\",\"bottom\",\"box-decoration-break\",\"box-shadow\",\"box-sizing\",\"break-after\",\"break-before\",\"break-inside\",\"caption-side\",\"caret-color\",\"clear\",\"clip\",\"clip-path\",\"clip-rule\",\"color\",\"column-count\",\"column-fill\",\"column-gap\",\"column-rule\",\"column-rule-color\",\"column-rule-style\",\"column-rule-width\",\"column-span\",\"column-width\",\"columns\",\"contain\",\"content\",\"content-visibility\",\"counter-increment\",\"counter-reset\",\"cue\",\"cue-after\",\"cue-before\",\"cursor\",\"direction\",\"display\",\"empty-cells\",\"filter\",\"flex\",\"flex-basis\",\"flex-direction\",\"flex-flow\",\"flex-grow\",\"flex-shrink\",\"flex-wrap\",\"float\",\"flow\",\"font\",\"font-display\",\"font-family\",\"font-feature-settings\",\"font-kerning\",\"font-language-override\",\"font-size\",\"font-size-adjust\",\"font-smoothing\",\"font-stretch\",\"font-style\",\"font-synthesis\",\"font-variant\",\"font-variant-caps\",\"font-variant-east-asian\",\"font-variant-ligatures\",\"font-variant-numeric\",\"font-variant-position\",\"font-variation-settings\",\"font-weight\",\"gap\",\"glyph-orientation-vertical\",\"grid\",\"grid-area\",\"grid-auto-columns\",\"grid-auto-flow\",\"grid-auto-rows\",\"grid-column\",\"grid-column-end\",\"grid-column-start\",\"grid-gap\",\"grid-row\",\"grid-row-end\",\"grid-row-start\",\"grid-template\",\"grid-template-areas\",\"grid-template-columns\",\"grid-template-rows\",\"hanging-punctuation\",\"height\",\"hyphens\",\"icon\",\"image-orientation\",\"image-rendering\",\"image-resolution\",\"ime-mode\",\"inline-size\",\"isolation\",\"justify-content\",\"left\",\"letter-spacing\",\"line-break\",\"line-height\",\"list-style\",\"list-style-image\",\"list-style-position\",\"list-style-type\",\"margin\",\"margin-block\",\"margin-block-end\",\"margin-block-start\",\"margin-bottom\",\"margin-inline\",\"margin-inline-end\",\"margin-inline-start\",\"margin-left\",\"margin-right\",\"margin-top\",\"marks\",\"mask\",\"mask-border\",\"mask-border-mode\",\"mask-border-outset\",\"mask-border-repeat\",\"mask-border-slice\",\"mask-border-source\",\"mask-border-width\",\"mask-clip\",\"mask-composite\",\"mask-image\",\"mask-mode\",\"mask-origin\",\"mask-position\",\"mask-repeat\",\"mask-size\",\"mask-type\",\"max-block-size\",\"max-height\",\"max-inline-size\",\"max-width\",\"min-block-size\",\"min-height\",\"min-inline-size\",\"min-width\",\"mix-blend-mode\",\"nav-down\",\"nav-index\",\"nav-left\",\"nav-right\",\"nav-up\",\"none\",\"normal\",\"object-fit\",\"object-position\",\"opacity\",\"order\",\"orphans\",\"outline\",\"outline-color\",\"outline-offset\",\"outline-style\",\"outline-width\",\"overflow\",\"overflow-wrap\",\"overflow-x\",\"overflow-y\",\"padding\",\"padding-block\",\"padding-block-end\",\"padding-block-start\",\"padding-bottom\",\"padding-inline\",\"padding-inline-end\",\"padding-inline-start\",\"padding-left\",\"padding-right\",\"padding-top\",\"page-break-after\",\"page-break-before\",\"page-break-inside\",\"pause\",\"pause-after\",\"pause-before\",\"perspective\",\"perspective-origin\",\"pointer-events\",\"position\",\"quotes\",\"resize\",\"rest\",\"rest-after\",\"rest-before\",\"right\",\"row-gap\",\"scroll-margin\",\"scroll-margin-block\",\"scroll-margin-block-end\",\"scroll-margin-block-start\",\"scroll-margin-bottom\",\"scroll-margin-inline\",\"scroll-margin-inline-end\",\"scroll-margin-inline-start\",\"scroll-margin-left\",\"scroll-margin-right\",\"scroll-margin-top\",\"scroll-padding\",\"scroll-padding-block\",\"scroll-padding-block-end\",\"scroll-padding-block-start\",\"scroll-padding-bottom\",\"scroll-padding-inline\",\"scroll-padding-inline-end\",\"scroll-padding-inline-start\",\"scroll-padding-left\",\"scroll-padding-right\",\"scroll-padding-top\",\"scroll-snap-align\",\"scroll-snap-stop\",\"scroll-snap-type\",\"scrollbar-color\",\"scrollbar-gutter\",\"scrollbar-width\",\"shape-image-threshold\",\"shape-margin\",\"shape-outside\",\"speak\",\"speak-as\",\"src\",\"tab-size\",\"table-layout\",\"text-align\",\"text-align-all\",\"text-align-last\",\"text-combine-upright\",\"text-decoration\",\"text-decoration-color\",\"text-decoration-line\",\"text-decoration-style\",\"text-emphasis\",\"text-emphasis-color\",\"text-emphasis-position\",\"text-emphasis-style\",\"text-indent\",\"text-justify\",\"text-orientation\",\"text-overflow\",\"text-rendering\",\"text-shadow\",\"text-transform\",\"text-underline-position\",\"top\",\"transform\",\"transform-box\",\"transform-origin\",\"transform-style\",\"transition\",\"transition-delay\",\"transition-duration\",\"transition-property\",\"transition-timing-function\",\"unicode-bidi\",\"vertical-align\",\"visibility\",\"voice-balance\",\"voice-duration\",\"voice-family\",\"voice-pitch\",\"voice-range\",\"voice-rate\",\"voice-stress\",\"voice-volume\",\"white-space\",\"widows\",\"width\",\"will-change\",\"word-break\",\"word-spacing\",\"word-wrap\",\"writing-mode\",\"z-index\"].reverse(),le=re.concat(se);var ce=\"\\\\.([0-9](_*[0-9])*)\",de=\"[0-9a-fA-F](_*[0-9a-fA-F])*\",ge={className:\"number\",variants:[{begin:`(\\\\b([0-9](_*[0-9])*)((${ce})|\\\\.)?|(${ce}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\\\b`},{begin:`\\\\b([0-9](_*[0-9])*)((${ce})[fFdD]?\\\\b|\\\\.([fFdD]\\\\b)?)`},{begin:`(${ce})[fFdD]?\\\\b`},{begin:\"\\\\b([0-9](_*[0-9])*)[fFdD]\\\\b\"},{begin:`\\\\b0[xX]((${de})\\\\.?|(${de})?\\\\.(${de}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\\\b`},{begin:\"\\\\b(0|[1-9](_*[0-9])*)[lL]?\\\\b\"},{begin:`\\\\b0[xX](${de})[lL]?\\\\b`},{begin:\"\\\\b0(_*[0-7])*[lL]?\\\\b\"},{begin:\"\\\\b0[bB][01](_*[01])*[lL]?\\\\b\"}],relevance:0};function ue(e,n,t){return-1===t?\"\":e.replace(n,a=>ue(e,n,t-1))}const be=\"[A-Za-z$_][0-9A-Za-z$_]*\",me=[\"as\",\"in\",\"of\",\"if\",\"for\",\"while\",\"finally\",\"var\",\"new\",\"function\",\"do\",\"return\",\"void\",\"else\",\"break\",\"catch\",\"instanceof\",\"with\",\"throw\",\"case\",\"default\",\"try\",\"case\",\"continue\",\"typeof\",\"delete\",\"let\",\"yield\",\"const\",\"class\",\"debugger\",\"async\",\"await\",\"static\",\"import\",\"from\",\"export\",\"extends\"],pe=[\"true\",\"false\",\"null\",\"undefined\",\"NaN\",\"Infinity\"],_e=[\"Object\",\"Function\",\"Boolean\",\"Symbol\",\"Math\",\"Date\",\"Number\",\"BigInt\",\"String\",\"RegExp\",\"Array\",\"Float32Array\",\"Float64Array\",\"Int8Array\",\"Uint8Array\",\"Uint8ClampedArray\",\"Int16Array\",\"Int32Array\",\"Uint16Array\",\"Uint32Array\",\"BigInt64Array\",\"BigUint64Array\",\"Set\",\"Map\",\"WeakSet\",\"WeakMap\",\"ArrayBuffer\",\"SharedArrayBuffer\",\"Atomics\",\"DataView\",\"JSON\",\"Promise\",\"Generator\",\"GeneratorFunction\",\"AsyncFunction\",\"Reflect\",\"Proxy\",\"Intl\",\"WebAssembly\"],he=[\"Error\",\"EvalError\",\"InternalError\",\"RangeError\",\"ReferenceError\",\"SyntaxError\",\"TypeError\",\"URIError\"],fe=[\"setInterval\",\"setTimeout\",\"clearInterval\",\"clearTimeout\",\"require\",\"exports\",\"eval\",\"isFinite\",\"isNaN\",\"parseFloat\",\"parseInt\",\"decodeURI\",\"decodeURIComponent\",\"encodeURI\",\"encodeURIComponent\",\"escape\",\"unescape\"],Ee=[\"arguments\",\"this\",\"super\",\"console\",\"window\",\"document\",\"localStorage\",\"module\",\"global\"],ye=[].concat(fe,_e,he);function we(e){const n=e.regex,t=be,a={begin:/<[A-Za-z0-9\\\\._:-]+/,end:/\\/[A-Za-z0-9\\\\._:-]+>|\\/>/,isTrulyOpeningTag:(e,n)=>{const t=e[0].length+e.index,a=e.input[t];if(\"<\"===a||\",\"===a)return void n.ignoreMatch();let i;\">\"===a&&(((e,{after:n})=>{const t=\"</\"+e[0].slice(1);return-1!==e.input.indexOf(t,n)})(e,{after:t})||n.ignoreMatch()),(i=e.input.substr(t).match(/^\\s+extends\\s+/))&&0===i.index&&n.ignoreMatch()}},i={$pattern:be,keyword:me,literal:pe,built_in:ye,\"variable.language\":Ee},r=\"\\\\.([0-9](_?[0-9])*)\",s=\"0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*\",o={className:\"number\",variants:[{begin:`(\\\\b(${s})((${r})|\\\\.)?|(${r}))[eE][+-]?([0-9](_?[0-9])*)\\\\b`},{begin:`\\\\b(${s})\\\\b((${r})\\\\b|\\\\.)?|(${r})\\\\b`},{begin:\"\\\\b(0|[1-9](_?[0-9])*)n\\\\b\"},{begin:\"\\\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\\\b\"},{begin:\"\\\\b0[bB][0-1](_?[0-1])*n?\\\\b\"},{begin:\"\\\\b0[oO][0-7](_?[0-7])*n?\\\\b\"},{begin:\"\\\\b0[0-7]+n?\\\\b\"}],relevance:0},l={className:\"subst\",begin:\"\\\\$\\\\{\",end:\"\\\\}\",keywords:i,contains:[]},c={begin:\"html`\",end:\"\",starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,l],subLanguage:\"xml\"}},d={begin:\"css`\",end:\"\",starts:{end:\"`\",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,l],subLanguage:\"css\"}},g={className:\"string\",begin:\"`\",end:\"`\",contains:[e.BACKSLASH_ESCAPE,l]},u={className:\"comment\",variants:[e.COMMENT(/\\/\\*\\*(?!\\/)/,\"\\\\*/\",{relevance:0,contains:[{begin:\"(?=@[A-Za-z]+)\",relevance:0,contains:[{className:\"doctag\",begin:\"@[A-Za-z]+\"},{className:\"type\",begin:\"\\\\{\",end:\"\\\\}\",excludeEnd:!0,excludeBegin:!0,relevance:0},{className:\"variable\",begin:t+\"(?=\\\\s*(-)|$)\",endsParent:!0,relevance:0},{begin:/(?=[^\\n])\\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]},b=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,c,d,g,o];l.contains=b.concat({begin:/\\{/,end:/\\}/,keywords:i,contains:[\"self\"].concat(b)});const m=[].concat(u,l.contains),p=m.concat([{begin:/\\(/,end:/\\)/,keywords:i,contains:[\"self\"].concat(m)}]),_={className:\"params\",begin:/\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:i,contains:p},h={variants:[{match:[/class/,/\\s+/,t,/\\s+/,/extends/,/\\s+/,n.concat(t,\"(\",n.concat(/\\./,t),\")*\")],scope:{1:\"keyword\",3:\"title.class\",5:\"keyword\",7:\"title.class.inherited\"}},{match:[/class/,/\\s+/,t],scope:{1:\"keyword\",3:\"title.class\"}}]},f={relevance:0,match:n.either(/\\bJSON/,/\\b[A-Z][a-z]+([A-Z][a-z]*|\\d)*/,/\\b[A-Z]{2,}([A-Z][a-z]+|\\d)+([A-Z][a-z]*)*/,/\\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\\d)*([A-Z][a-z]*)*/),className:\"title.class\",keywords:{_:[..._e,...he]}},E={variants:[{match:[/function/,/\\s+/,t,/(?=\\s*\\()/]},{match:[/function/,/\\s*(?=\\()/]}],className:{1:\"keyword\",3:\"title.function\"},label:\"func.def\",contains:[_],illegal:/%/},y={match:n.concat(/\\b/,(w=[...fe,\"super\"],n.concat(\"(?!\",w.join(\"|\"),\")\")),t,n.lookahead(/\\(/)),className:\"title.function\",relevance:0};var w;const N={begin:n.concat(/\\./,n.lookahead(n.concat(t,/(?![0-9A-Za-z$_(])/))),end:t,excludeBegin:!0,keywords:\"prototype\",className:\"property\",relevance:0},v={match:[/get|set/,/\\s+/,t,/(?=\\()/],className:{1:\"keyword\",3:\"title.function\"},contains:[{begin:/\\(\\)/},_]},k=\"(\\\\([^()]*(\\\\([^()]*(\\\\([^()]*\\\\)[^()]*)*\\\\)[^()]*)*\\\\)|\"+e.UNDERSCORE_IDENT_RE+\")\\\\s*=>\",O={match:[/const|var|let/,/\\s+/,t,/\\s*/,/=\\s*/,/(async\\s*)?/,n.lookahead(k)],keywords:\"async\",className:{1:\"keyword\",3:\"title.function\"},contains:[_]};return{name:\"Javascript\",aliases:[\"js\",\"jsx\",\"mjs\",\"cjs\"],keywords:i,exports:{PARAMS_CONTAINS:p,CLASS_REFERENCE:f},illegal:/#(?![$_A-z])/,contains:[e.SHEBANG({label:\"shebang\",binary:\"node\",relevance:5}),{label:\"use_strict\",className:\"meta\",relevance:10,begin:/^\\s*['\"]use (strict|asm)['\"]/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,c,d,g,u,o,f,{className:\"attr\",begin:t+n.lookahead(\":\"),relevance:0},O,{begin:\"(\"+e.RE_STARTERS_RE+\"|\\\\b(case|return|throw)\\\\b)\\\\s*\",keywords:\"return throw case\",relevance:0,contains:[u,e.REGEXP_MODE,{className:\"function\",begin:k,returnBegin:!0,end:\"\\\\s*=>\",contains:[{className:\"params\",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\\(\\s*\\)/,skip:!0},{begin:/\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:i,contains:p}]}]},{begin:/,/,relevance:0},{match:/\\s+/,relevance:0},{variants:[{begin:\"<>\",end:\"</>\"},{match:/<[A-Za-z0-9\\\\._:-]+\\s*\\/>/},{begin:a.begin,\"on:begin\":a.isTrulyOpeningTag,end:a.end}],subLanguage:\"xml\",contains:[{begin:a.begin,end:a.end,skip:!0,contains:[\"self\"]}]}]},E,{beginKeywords:\"while if case catch for\"},{begin:\"\\\\b(?!function)\"+e.UNDERSCORE_IDENT_RE+\"\\\\([^()]*(\\\\([^()]*(\\\\([^()]*\\\\)[^()]*)*\\\\)[^()]*)*\\\\)\\\\s*\\\\{\",returnBegin:!0,label:\"func.def\",contains:[_,e.inherit(e.TITLE_MODE,{begin:t,className:\"title.function\"})]},{match:/\\.\\.\\./,relevance:0},N,{match:\"\\\\$\"+t,relevance:0},{match:[/\\bconstructor(?=\\s*\\()/],className:{1:\"title.function\"},contains:[_]},y,{relevance:0,match:/\\b[A-Z][A-Z_0-9]+\\b/,className:\"variable.constant\"},h,v,{match:/\\$[(.]/}]}}const Ne=e=>m(/\\b/,e,/\\w$/.test(e)?/\\b/:/\\B/),ve=[\"Protocol\",\"Type\"].map(Ne),ke=[\"init\",\"self\"].map(Ne),Oe=[\"Any\",\"Self\"],xe=[\"actor\",\"associatedtype\",\"async\",\"await\",/as\\?/,/as!/,\"as\",\"break\",\"case\",\"catch\",\"class\",\"continue\",\"convenience\",\"default\",\"defer\",\"deinit\",\"didSet\",\"do\",\"dynamic\",\"else\",\"enum\",\"extension\",\"fallthrough\",/fileprivate\\(set\\)/,\"fileprivate\",\"final\",\"for\",\"func\",\"get\",\"guard\",\"if\",\"import\",\"indirect\",\"infix\",/init\\?/,/init!/,\"inout\",/internal\\(set\\)/,\"internal\",\"in\",\"is\",\"isolated\",\"nonisolated\",\"lazy\",\"let\",\"mutating\",\"nonmutating\",/open\\(set\\)/,\"open\",\"operator\",\"optional\",\"override\",\"postfix\",\"precedencegroup\",\"prefix\",/private\\(set\\)/,\"private\",\"protocol\",/public\\(set\\)/,\"public\",\"repeat\",\"required\",\"rethrows\",\"return\",\"set\",\"some\",\"static\",\"struct\",\"subscript\",\"super\",\"case\",\"throws\",\"throw\",/try\\?/,/try!/,\"try\",\"typealias\",/unowned\\(safe\\)/,/unowned\\(unsafe\\)/,\"unowned\",\"var\",\"weak\",\"where\",\"while\",\"willSet\"],Me=[\"false\",\"nil\",\"true\"],Se=[\"assignment\",\"associativity\",\"higherThan\",\"left\",\"lowerThan\",\"none\",\"right\"],Ae=[\"#colorLiteral\",\"#column\",\"#dsohandle\",\"#else\",\"#elseif\",\"#endif\",\"#error\",\"#file\",\"#fileID\",\"#fileLiteral\",\"#filePath\",\"#function\",\"#if\",\"#imageLiteral\",\"#keyPath\",\"#line\",\"#selector\",\"#sourceLocation\",\"#warn_unqualified_access\",\"#warning\"],Ce=[\"abs\",\"all\",\"any\",\"assert\",\"assertionFailure\",\"debugPrint\",\"dump\",\"fatalError\",\"getVaList\",\"isKnownUniquelyReferenced\",\"max\",\"min\",\"numericCast\",\"pointwiseMax\",\"pointwiseMin\",\"precondition\",\"preconditionFailure\",\"print\",\"readLine\",\"repeatElement\",\"sequence\",\"stride\",\"swap\",\"swift_unboxFromSwiftValueWithType\",\"transcode\",\"type\",\"unsafeBitCast\",\"unsafeDowncast\",\"withExtendedLifetime\",\"withUnsafeMutablePointer\",\"withUnsafePointer\",\"withVaList\",\"withoutActuallyEscaping\",\"zip\"],Te=p(/[/=\\-+!*%<>&|^~?]/,/[\\u00A1-\\u00A7]/,/[\\u00A9\\u00AB]/,/[\\u00AC\\u00AE]/,/[\\u00B0\\u00B1]/,/[\\u00B6\\u00BB\\u00BF\\u00D7\\u00F7]/,/[\\u2016-\\u2017]/,/[\\u2020-\\u2027]/,/[\\u2030-\\u203E]/,/[\\u2041-\\u2053]/,/[\\u2055-\\u205E]/,/[\\u2190-\\u23FF]/,/[\\u2500-\\u2775]/,/[\\u2794-\\u2BFF]/,/[\\u2E00-\\u2E7F]/,/[\\u3001-\\u3003]/,/[\\u3008-\\u3020]/,/[\\u3030]/),Re=p(Te,/[\\u0300-\\u036F]/,/[\\u1DC0-\\u1DFF]/,/[\\u20D0-\\u20FF]/,/[\\uFE00-\\uFE0F]/,/[\\uFE20-\\uFE2F]/),De=m(Te,Re,\"*\"),Ie=p(/[a-zA-Z_]/,/[\\u00A8\\u00AA\\u00AD\\u00AF\\u00B2-\\u00B5\\u00B7-\\u00BA]/,/[\\u00BC-\\u00BE\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF]/,/[\\u0100-\\u02FF\\u0370-\\u167F\\u1681-\\u180D\\u180F-\\u1DBF]/,/[\\u1E00-\\u1FFF]/,/[\\u200B-\\u200D\\u202A-\\u202E\\u203F-\\u2040\\u2054\\u2060-\\u206F]/,/[\\u2070-\\u20CF\\u2100-\\u218F\\u2460-\\u24FF\\u2776-\\u2793]/,/[\\u2C00-\\u2DFF\\u2E80-\\u2FFF]/,/[\\u3004-\\u3007\\u3021-\\u302F\\u3031-\\u303F\\u3040-\\uD7FF]/,/[\\uF900-\\uFD3D\\uFD40-\\uFDCF\\uFDF0-\\uFE1F\\uFE30-\\uFE44]/,/[\\uFE47-\\uFEFE\\uFF00-\\uFFFD]/),Le=p(Ie,/\\d/,/[\\u0300-\\u036F\\u1DC0-\\u1DFF\\u20D0-\\u20FF\\uFE20-\\uFE2F]/),Be=m(Ie,Le,\"*\"),$e=m(/[A-Z]/,Le,\"*\"),ze=[\"autoclosure\",m(/convention\\(/,p(\"swift\",\"block\",\"c\"),/\\)/),\"discardableResult\",\"dynamicCallable\",\"dynamicMemberLookup\",\"escaping\",\"frozen\",\"GKInspectable\",\"IBAction\",\"IBDesignable\",\"IBInspectable\",\"IBOutlet\",\"IBSegueAction\",\"inlinable\",\"main\",\"nonobjc\",\"NSApplicationMain\",\"NSCopying\",\"NSManaged\",m(/objc\\(/,Be,/\\)/),\"objc\",\"objcMembers\",\"propertyWrapper\",\"requires_stored_property_inits\",\"resultBuilder\",\"testable\",\"UIApplicationMain\",\"unknown\",\"usableFromInline\"],Fe=[\"iOS\",\"iOSApplicationExtension\",\"macOS\",\"macOSApplicationExtension\",\"macCatalyst\",\"macCatalystApplicationExtension\",\"watchOS\",\"watchOSApplicationExtension\",\"tvOS\",\"tvOSApplicationExtension\",\"swift\"];var Ue=Object.freeze({__proto__:null,grmr_bash:e=>{const n=e.regex,t={},a={begin:/\\$\\{/,end:/\\}/,contains:[\"self\",{begin:/:-/,contains:[t]}]};Object.assign(t,{className:\"variable\",variants:[{begin:n.concat(/\\$[\\w\\d#@][\\w\\d_]*/,\"(?![\\\\w\\\\d])(?![$])\")},a]});const i={className:\"subst\",begin:/\\$\\(/,end:/\\)/,contains:[e.BACKSLASH_ESCAPE]},r={begin:/<<-?\\s*(?=\\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\\w+)/,end:/(\\w+)/,className:\"string\"})]}},s={className:\"string\",begin:/\"/,end:/\"/,contains:[e.BACKSLASH_ESCAPE,t,i]};i.contains.push(s);const o={begin:/\\$\\(\\(/,end:/\\)\\)/,contains:[{begin:/\\d+#[0-9a-f]+/,className:\"number\"},e.NUMBER_MODE,t]},l=e.SHEBANG({binary:\"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)\",relevance:10}),c={className:\"function\",begin:/\\w[\\w\\d_]*\\s*\\(\\s*\\)\\s*\\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\\w[\\w\\d_]*/})],relevance:0};return{name:\"Bash\",aliases:[\"sh\"],keywords:{$pattern:/\\b[a-z][a-z0-9._-]+\\b/,keyword:[\"if\",\"then\",\"else\",\"elif\",\"fi\",\"for\",\"while\",\"in\",\"do\",\"done\",\"case\",\"esac\",\"function\"],literal:[\"true\",\"false\"],built_in:[\"break\",\"cd\",\"continue\",\"eval\",\"exec\",\"exit\",\"export\",\"getopts\",\"hash\",\"pwd\",\"readonly\",\"return\",\"shift\",\"test\",\"times\",\"trap\",\"umask\",\"unset\",\"alias\",\"bind\",\"builtin\",\"caller\",\"command\",\"declare\",\"echo\",\"enable\",\"help\",\"let\",\"local\",\"logout\",\"mapfile\",\"printf\",\"read\",\"readarray\",\"source\",\"type\",\"typeset\",\"ulimit\",\"unalias\",\"set\",\"shopt\",\"autoload\",\"bg\",\"bindkey\",\"bye\",\"cap\",\"chdir\",\"clone\",\"comparguments\",\"compcall\",\"compctl\",\"compdescribe\",\"compfiles\",\"compgroups\",\"compquote\",\"comptags\",\"comptry\",\"compvalues\",\"dirs\",\"disable\",\"disown\",\"echotc\",\"echoti\",\"emulate\",\"fc\",\"fg\",\"float\",\"functions\",\"getcap\",\"getln\",\"history\",\"integer\",\"jobs\",\"kill\",\"limit\",\"log\",\"noglob\",\"popd\",\"print\",\"pushd\",\"pushln\",\"rehash\",\"sched\",\"setcap\",\"setopt\",\"stat\",\"suspend\",\"ttyctl\",\"unfunction\",\"unhash\",\"unlimit\",\"unsetopt\",\"vared\",\"wait\",\"whence\",\"where\",\"which\",\"zcompile\",\"zformat\",\"zftp\",\"zle\",\"zmodload\",\"zparseopts\",\"zprof\",\"zpty\",\"zregexparse\",\"zsocket\",\"zstyle\",\"ztcp\",\"chcon\",\"chgrp\",\"chown\",\"chmod\",\"cp\",\"dd\",\"df\",\"dir\",\"dircolors\",\"ln\",\"ls\",\"mkdir\",\"mkfifo\",\"mknod\",\"mktemp\",\"mv\",\"realpath\",\"rm\",\"rmdir\",\"shred\",\"sync\",\"touch\",\"truncate\",\"vdir\",\"b2sum\",\"base32\",\"base64\",\"cat\",\"cksum\",\"comm\",\"csplit\",\"cut\",\"expand\",\"fmt\",\"fold\",\"head\",\"join\",\"md5sum\",\"nl\",\"numfmt\",\"od\",\"paste\",\"ptx\",\"pr\",\"sha1sum\",\"sha224sum\",\"sha256sum\",\"sha384sum\",\"sha512sum\",\"shuf\",\"sort\",\"split\",\"sum\",\"tac\",\"tail\",\"tr\",\"tsort\",\"unexpand\",\"uniq\",\"wc\",\"arch\",\"basename\",\"chroot\",\"date\",\"dirname\",\"du\",\"echo\",\"env\",\"expr\",\"factor\",\"groups\",\"hostid\",\"id\",\"link\",\"logname\",\"nice\",\"nohup\",\"nproc\",\"pathchk\",\"pinky\",\"printenv\",\"printf\",\"pwd\",\"readlink\",\"runcon\",\"seq\",\"sleep\",\"stat\",\"stdbuf\",\"stty\",\"tee\",\"test\",\"timeout\",\"tty\",\"uname\",\"unlink\",\"uptime\",\"users\",\"who\",\"whoami\",\"yes\"]},contains:[l,e.SHEBANG(),c,o,e.HASH_COMMENT_MODE,r,{match:/(\\/[a-z._-]+)+/},s,{className:\"\",begin:/\\\\\"/},{className:\"string\",begin:/'/,end:/'/},t]}},grmr_c:e=>{const n=e.regex,t=e.COMMENT(\"//\",\"$\",{contains:[{begin:/\\\\\\n/}]}),a=\"[a-zA-Z_]\\\\w*::\",i=\"(decltype\\\\(auto\\\\)|\"+n.optional(a)+\"[a-zA-Z_]\\\\w*\"+n.optional(\"<[^<>]+>\")+\")\",r={className:\"type\",variants:[{begin:\"\\\\b[a-z\\\\d_]*_t\\\\b\"},{match:/\\batomic_[a-z]{3,6}\\b/}]},s={className:\"string\",variants:[{begin:'(u8?|U|L)?\"',end:'\"',illegal:\"\\\\n\",contains:[e.BACKSLASH_ESCAPE]},{begin:\"(u8?|U|L)?'(\\\\\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\\\S)|.)\",end:\"'\",illegal:\".\"},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R\"([^()\\\\ ]{0,16})\\(/,end:/\\)([^()\\\\ ]{0,16})\"/})]},o={className:\"number\",variants:[{begin:\"\\\\b(0b[01']+)\"},{begin:\"(-?)\\\\b([\\\\d']+(\\\\.[\\\\d']*)?|\\\\.[\\\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)\"},{begin:\"(-?)(\\\\b0[xX][a-fA-F0-9']+|(\\\\b[\\\\d']+(\\\\.[\\\\d']*)?|\\\\.[\\\\d']+)([eE][-+]?[\\\\d']+)?)\"}],relevance:0},l={className:\"meta\",begin:/#\\s*[a-z]+\\b/,end:/$/,keywords:{keyword:\"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include\"},contains:[{begin:/\\\\\\n/,relevance:0},e.inherit(s,{className:\"string\"}),{className:\"string\",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},c={className:\"title\",begin:n.optional(a)+e.IDENT_RE,relevance:0},d=n.optional(a)+e.IDENT_RE+\"\\\\s*\\\\(\",g={keyword:[\"asm\",\"auto\",\"break\",\"case\",\"continue\",\"default\",\"do\",\"else\",\"enum\",\"extern\",\"for\",\"fortran\",\"goto\",\"if\",\"inline\",\"register\",\"restrict\",\"return\",\"sizeof\",\"struct\",\"case\",\"typedef\",\"union\",\"volatile\",\"while\",\"_Alignas\",\"_Alignof\",\"_Atomic\",\"_Generic\",\"_Noreturn\",\"_Static_assert\",\"_Thread_local\",\"alignas\",\"alignof\",\"noreturn\",\"static_assert\",\"thread_local\",\"_Pragma\"],type:[\"float\",\"double\",\"signed\",\"unsigned\",\"int\",\"short\",\"long\",\"char\",\"void\",\"_Bool\",\"_Complex\",\"_Imaginary\",\"_Decimal32\",\"_Decimal64\",\"_Decimal128\",\"const\",\"static\",\"complex\",\"bool\",\"imaginary\"],literal:\"true false NULL\",built_in:\"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr\"},u=[l,r,t,e.C_BLOCK_COMMENT_MODE,o,s],b={variants:[{begin:/=/,end:/;/},{begin:/\\(/,end:/\\)/},{beginKeywords:\"new throw return else\",end:/;/}],keywords:g,contains:u.concat([{begin:/\\(/,end:/\\)/,keywords:g,contains:u.concat([\"self\"]),relevance:0}]),relevance:0},m={begin:\"(\"+i+\"[\\\\*&\\\\s]+)+\"+d,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:g,illegal:/[^\\w\\s\\*&:<>.]/,contains:[{begin:\"decltype\\\\(auto\\\\)\",keywords:g,relevance:0},{begin:d,returnBegin:!0,contains:[e.inherit(c,{className:\"title.function\"})],relevance:0},{relevance:0,match:/,/},{className:\"params\",begin:/\\(/,end:/\\)/,keywords:g,relevance:0,contains:[t,e.C_BLOCK_COMMENT_MODE,s,o,r,{begin:/\\(/,end:/\\)/,keywords:g,relevance:0,contains:[\"self\",t,e.C_BLOCK_COMMENT_MODE,s,o,r]}]},r,t,e.C_BLOCK_COMMENT_MODE,l]};return{name:\"C\",aliases:[\"h\"],keywords:g,disableAutodetect:!0,illegal:\"</\",contains:[].concat(b,m,u,[l,{begin:e.IDENT_RE+\"::\",keywords:g},{className:\"class\",beginKeywords:\"enum class struct union\",end:/[{;:<>=]/,contains:[{beginKeywords:\"final class struct\"},e.TITLE_MODE]}]),exports:{preprocessor:l,strings:s,keywords:g}}},grmr_cpp:e=>{const n=e.regex,t=e.COMMENT(\"//\",\"$\",{contains:[{begin:/\\\\\\n/}]}),a=\"[a-zA-Z_]\\\\w*::\",i=\"(?!struct)(decltype\\\\(auto\\\\)|\"+n.optional(a)+\"[a-zA-Z_]\\\\w*\"+n.optional(\"<[^<>]+>\")+\")\",r={className:\"type\",begin:\"\\\\b[a-z\\\\d_]*_t\\\\b\"},s={className:\"string\",variants:[{begin:'(u8?|U|L)?\"',end:'\"',illegal:\"\\\\n\",contains:[e.BACKSLASH_ESCAPE]},{begin:\"(u8?|U|L)?'(\\\\\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\\\S)|.)\",end:\"'\",illegal:\".\"},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R\"([^()\\\\ ]{0,16})\\(/,end:/\\)([^()\\\\ ]{0,16})\"/})]},o={className:\"number\",variants:[{begin:\"\\\\b(0b[01']+)\"},{begin:\"(-?)\\\\b([\\\\d']+(\\\\.[\\\\d']*)?|\\\\.[\\\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)\"},{begin:\"(-?)(\\\\b0[xX][a-fA-F0-9']+|(\\\\b[\\\\d']+(\\\\.[\\\\d']*)?|\\\\.[\\\\d']+)([eE][-+]?[\\\\d']+)?)\"}],relevance:0},l={className:\"meta\",begin:/#\\s*[a-z]+\\b/,end:/$/,keywords:{keyword:\"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include\"},contains:[{begin:/\\\\\\n/,relevance:0},e.inherit(s,{className:\"string\"}),{className:\"string\",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},c={className:\"title\",begin:n.optional(a)+e.IDENT_RE,relevance:0},d=n.optional(a)+e.IDENT_RE+\"\\\\s*\\\\(\",g={type:[\"bool\",\"char\",\"char16_t\",\"char32_t\",\"char8_t\",\"double\",\"float\",\"int\",\"long\",\"short\",\"void\",\"wchar_t\",\"unsigned\",\"signed\",\"const\",\"static\"],keyword:[\"alignas\",\"alignof\",\"and\",\"and_eq\",\"asm\",\"atomic_cancel\",\"atomic_commit\",\"atomic_noexcept\",\"auto\",\"bitand\",\"bitor\",\"break\",\"case\",\"catch\",\"class\",\"co_await\",\"co_return\",\"co_yield\",\"compl\",\"concept\",\"const_cast|10\",\"consteval\",\"constexpr\",\"constinit\",\"continue\",\"decltype\",\"default\",\"delete\",\"do\",\"dynamic_cast|10\",\"else\",\"enum\",\"explicit\",\"export\",\"extern\",\"false\",\"final\",\"for\",\"friend\",\"goto\",\"if\",\"import\",\"inline\",\"module\",\"mutable\",\"namespace\",\"new\",\"noexcept\",\"not\",\"not_eq\",\"nullptr\",\"operator\",\"or\",\"or_eq\",\"override\",\"private\",\"protected\",\"public\",\"reflexpr\",\"register\",\"reinterpret_cast|10\",\"requires\",\"return\",\"sizeof\",\"static_assert\",\"static_cast|10\",\"struct\",\"case\",\"synchronized\",\"template\",\"this\",\"thread_local\",\"throw\",\"transaction_safe\",\"transaction_safe_dynamic\",\"true\",\"try\",\"typedef\",\"typeid\",\"typename\",\"union\",\"using\",\"virtual\",\"volatile\",\"while\",\"xor\",\"xor_eq\"],literal:[\"NULL\",\"false\",\"nullopt\",\"nullptr\",\"true\"],built_in:[\"_Pragma\"],_type_hints:[\"any\",\"auto_ptr\",\"barrier\",\"binary_semaphore\",\"bitset\",\"complex\",\"condition_variable\",\"condition_variable_any\",\"counting_semaphore\",\"deque\",\"false_type\",\"future\",\"imaginary\",\"initializer_list\",\"istringstream\",\"jthread\",\"latch\",\"lock_guard\",\"multimap\",\"multiset\",\"mutex\",\"optional\",\"ostringstream\",\"packaged_task\",\"pair\",\"promise\",\"priority_queue\",\"queue\",\"recursive_mutex\",\"recursive_timed_mutex\",\"scoped_lock\",\"set\",\"shared_future\",\"shared_lock\",\"shared_mutex\",\"shared_timed_mutex\",\"shared_ptr\",\"stack\",\"string_view\",\"stringstream\",\"timed_mutex\",\"thread\",\"true_type\",\"tuple\",\"unique_lock\",\"unique_ptr\",\"unordered_map\",\"unordered_multimap\",\"unordered_multiset\",\"unordered_set\",\"variant\",\"vector\",\"weak_ptr\",\"wstring\",\"wstring_view\"]},u={className:\"function.dispatch\",relevance:0,keywords:{_hint:[\"abort\",\"abs\",\"acos\",\"apply\",\"as_const\",\"asin\",\"atan\",\"atan2\",\"calloc\",\"ceil\",\"cerr\",\"cin\",\"clog\",\"cos\",\"cosh\",\"cout\",\"declval\",\"endl\",\"exchange\",\"exit\",\"exp\",\"fabs\",\"floor\",\"fmod\",\"forward\",\"fprintf\",\"fputs\",\"free\",\"frexp\",\"fscanf\",\"future\",\"invoke\",\"isalnum\",\"isalpha\",\"iscntrl\",\"isdigit\",\"isgraph\",\"islower\",\"isprint\",\"ispunct\",\"isspace\",\"isupper\",\"isxdigit\",\"labs\",\"launder\",\"ldexp\",\"log\",\"log10\",\"make_pair\",\"make_shared\",\"make_shared_for_overwrite\",\"make_tuple\",\"make_unique\",\"malloc\",\"memchr\",\"memcmp\",\"memcpy\",\"memset\",\"modf\",\"move\",\"pow\",\"printf\",\"putchar\",\"puts\",\"realloc\",\"scanf\",\"sin\",\"sinh\",\"snprintf\",\"sprintf\",\"sqrt\",\"sscanf\",\"std\",\"stderr\",\"stdin\",\"stdout\",\"strcat\",\"strchr\",\"strcmp\",\"strcpy\",\"strcspn\",\"strlen\",\"strncat\",\"strncmp\",\"strncpy\",\"strpbrk\",\"strrchr\",\"strspn\",\"strstr\",\"swap\",\"tan\",\"tanh\",\"terminate\",\"to_underlying\",\"tolower\",\"toupper\",\"vfprintf\",\"visit\",\"vprintf\",\"vsprintf\"]},begin:n.concat(/\\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!case)/,/(?!while)/,e.IDENT_RE,n.lookahead(/(<[^<>]+>|)\\s*\\(/))},b=[u,l,r,t,e.C_BLOCK_COMMENT_MODE,o,s],m={variants:[{begin:/=/,end:/;/},{begin:/\\(/,end:/\\)/},{beginKeywords:\"new throw return else\",end:/;/}],keywords:g,contains:b.concat([{begin:/\\(/,end:/\\)/,keywords:g,contains:b.concat([\"self\"]),relevance:0}]),relevance:0},p={className:\"function\",begin:\"(\"+i+\"[\\\\*&\\\\s]+)+\"+d,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:g,illegal:/[^\\w\\s\\*&:<>.]/,contains:[{begin:\"decltype\\\\(auto\\\\)\",keywords:g,relevance:0},{begin:d,returnBegin:!0,contains:[c],relevance:0},{begin:/::/,relevance:0},{begin:/:/,endsWithParent:!0,contains:[s,o]},{relevance:0,match:/,/},{className:\"params\",begin:/\\(/,end:/\\)/,keywords:g,relevance:0,contains:[t,e.C_BLOCK_COMMENT_MODE,s,o,r,{begin:/\\(/,end:/\\)/,keywords:g,relevance:0,contains:[\"self\",t,e.C_BLOCK_COMMENT_MODE,s,o,r]}]},r,t,e.C_BLOCK_COMMENT_MODE,l]};return{name:\"C++\",aliases:[\"cc\",\"c++\",\"h++\",\"hpp\",\"hh\",\"hxx\",\"cxx\"],keywords:g,illegal:\"</\",classNameAliases:{\"function.dispatch\":\"built_in\"},contains:[].concat(m,p,u,b,[l,{begin:\"\\\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array|tuple|optional|variant|function)\\\\s*<(?!<)\",end:\">\",keywords:g,contains:[\"self\",r]},{begin:e.IDENT_RE+\"::\",keywords:g},{match:[/\\b(?:enum(?:\\s+(?:class|struct))?|class|struct|union)/,/\\s+/,/\\w+/],className:{1:\"keyword\",3:\"title.class\"}}])}},grmr_csharp:e=>{const n={keyword:[\"abstract\",\"as\",\"base\",\"break\",\"case\",\"catch\",\"class\",\"const\",\"continue\",\"do\",\"else\",\"event\",\"explicit\",\"extern\",\"finally\",\"fixed\",\"for\",\"foreach\",\"goto\",\"if\",\"implicit\",\"in\",\"interface\",\"internal\",\"is\",\"lock\",\"namespace\",\"new\",\"operator\",\"out\",\"override\",\"params\",\"private\",\"protected\",\"public\",\"readonly\",\"record\",\"ref\",\"return\",\"sealed\",\"sizeof\",\"stackalloc\",\"static\",\"struct\",\"case\",\"this\",\"throw\",\"try\",\"typeof\",\"unchecked\",\"unsafe\",\"using\",\"virtual\",\"void\",\"volatile\",\"while\"].concat([\"add\",\"alias\",\"and\",\"ascending\",\"async\",\"await\",\"by\",\"descending\",\"equals\",\"from\",\"get\",\"global\",\"group\",\"init\",\"into\",\"join\",\"let\",\"nameof\",\"not\",\"notnull\",\"on\",\"or\",\"orderby\",\"partial\",\"remove\",\"select\",\"set\",\"unmanaged\",\"value|0\",\"var\",\"when\",\"where\",\"with\",\"yield\"]),built_in:[\"bool\",\"byte\",\"char\",\"decimal\",\"delegate\",\"double\",\"dynamic\",\"enum\",\"float\",\"int\",\"long\",\"nint\",\"nuint\",\"object\",\"sbyte\",\"short\",\"string\",\"ulong\",\"uint\",\"ushort\"],literal:[\"default\",\"false\",\"null\",\"true\"]},t=e.inherit(e.TITLE_MODE,{begin:\"[a-zA-Z](\\\\.?\\\\w)*\"}),a={className:\"number\",variants:[{begin:\"\\\\b(0b[01']+)\"},{begin:\"(-?)\\\\b([\\\\d']+(\\\\.[\\\\d']*)?|\\\\.[\\\\d']+)(u|U|l|L|ul|UL|f|F|b|B)\"},{begin:\"(-?)(\\\\b0[xX][a-fA-F0-9']+|(\\\\b[\\\\d']+(\\\\.[\\\\d']*)?|\\\\.[\\\\d']+)([eE][-+]?[\\\\d']+)?)\"}],relevance:0},i={className:\"string\",begin:'@\"',end:'\"',contains:[{begin:'\"\"'}]},r=e.inherit(i,{illegal:/\\n/}),s={className:\"subst\",begin:/\\{/,end:/\\}/,keywords:n},o=e.inherit(s,{illegal:/\\n/}),l={className:\"string\",begin:/\\$\"/,end:'\"',illegal:/\\n/,contains:[{begin:/\\{\\{/},{begin:/\\}\\}/},e.BACKSLASH_ESCAPE,o]},c={className:\"string\",begin:/\\$@\"/,end:'\"',contains:[{begin:/\\{\\{/},{begin:/\\}\\}/},{begin:'\"\"'},s]},d=e.inherit(c,{illegal:/\\n/,contains:[{begin:/\\{\\{/},{begin:/\\}\\}/},{begin:'\"\"'},o]});s.contains=[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],o.contains=[d,l,r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\\n/})];const g={variants:[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},u={begin:\"<\",end:\">\",contains:[{beginKeywords:\"in out\"},t]},b=e.IDENT_RE+\"(<\"+e.IDENT_RE+\"(\\\\s*,\\\\s*\"+e.IDENT_RE+\")*>)?(\\\\[\\\\])?\",m={begin:\"@\"+e.IDENT_RE,relevance:0};return{name:\"C#\",aliases:[\"cs\",\"c#\"],keywords:n,illegal:/::/,contains:[e.COMMENT(\"///\",\"$\",{returnBegin:!0,contains:[{className:\"doctag\",variants:[{begin:\"///\",relevance:0},{begin:\"\\x3c!--|--\\x3e\"},{begin:\"</?\",end:\">\"}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:\"meta\",begin:\"#\",end:\"$\",keywords:{keyword:\"if else elif endif define undef warning error line region endregion pragma checksum\"}},g,a,{beginKeywords:\"class interface\",relevance:0,end:/[{;=]/,illegal:/[^\\s:,]/,contains:[{beginKeywords:\"where class\"},t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:\"namespace\",relevance:0,end:/[{;=]/,illegal:/[^\\s:]/,contains:[t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:\"record\",relevance:0,end:/[{;=]/,illegal:/[^\\s:]/,contains:[t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:\"meta\",begin:\"^\\\\s*\\\\[(?=[\\\\w])\",excludeBegin:!0,end:\"\\\\]\",excludeEnd:!0,contains:[{className:\"string\",begin:/\"/,end:/\"/}]},{beginKeywords:\"new return throw await else\",relevance:0},{className:\"function\",begin:\"(\"+b+\"\\\\s+)+\"+e.IDENT_RE+\"\\\\s*(<[^=]+>\\\\s*)?\\\\(\",returnBegin:!0,end:/\\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{beginKeywords:\"public private protected static internal protected abstract async extern override unsafe virtual new sealed partial\",relevance:0},{begin:e.IDENT_RE+\"\\\\s*(<[^=]+>\\\\s*)?\\\\(\",returnBegin:!0,contains:[e.TITLE_MODE,u],relevance:0},{match:/\\(\\)/},{className:\"params\",begin:/\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[g,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},m]}},grmr_css:e=>{const n=e.regex,t=te(e),a=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE];return{name:\"CSS\",case_insensitive:!0,illegal:/[=|'\\$]/,keywords:{keyframePosition:\"from to\"},classNameAliases:{keyframePosition:\"selector-tag\"},contains:[t.BLOCK_COMMENT,{begin:/-(webkit|moz|ms|o)-(?=[a-z])/},t.CSS_NUMBER_MODE,{className:\"selector-id\",begin:/#[A-Za-z0-9_-]+/,relevance:0},{className:\"selector-class\",begin:\"\\\\.[a-zA-Z-][a-zA-Z0-9_-]*\",relevance:0},t.ATTRIBUTE_SELECTOR_MODE,{className:\"selector-pseudo\",variants:[{begin:\":(\"+re.join(\"|\")+\")\"},{begin:\":(:)?(\"+se.join(\"|\")+\")\"}]},t.CSS_VARIABLE,{className:\"attribute\",begin:\"\\\\b(\"+oe.join(\"|\")+\")\\\\b\"},{begin:/:/,end:/[;}{]/,contains:[t.BLOCK_COMMENT,t.HEXCOLOR,t.IMPORTANT,t.CSS_NUMBER_MODE,...a,{begin:/(url|data-uri)\\(/,end:/\\)/,relevance:0,keywords:{built_in:\"url data-uri\"},contains:[{className:\"string\",begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}]},t.FUNCTION_DISPATCH]},{begin:n.lookahead(/@/),end:\"[{;]\",relevance:0,illegal:/:/,contains:[{className:\"keyword\",begin:/@-?\\w[\\w]*(-\\w+)*/},{begin:/\\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{$pattern:/[a-z-]+/,keyword:\"and or not only\",attribute:ie.join(\" \")},contains:[{begin:/[a-z-]+(?=:)/,className:\"attribute\"},...a,t.CSS_NUMBER_MODE]}]},{className:\"selector-tag\",begin:\"\\\\b(\"+ae.join(\"|\")+\")\\\\b\"}]}},grmr_diff:e=>{const n=e.regex;return{name:\"Diff\",aliases:[\"patch\"],contains:[{className:\"meta\",relevance:10,match:n.either(/^@@ +-\\d+,\\d+ +\\+\\d+,\\d+ +@@/,/^\\*\\*\\* +\\d+,\\d+ +\\*\\*\\*\\*$/,/^--- +\\d+,\\d+ +----$/)},{className:\"comment\",variants:[{begin:n.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\\*{3} /,/^\\+{3}/,/^diff --git/),end:/$/},{match:/^\\*{15}$/}]},{className:\"addition\",begin:/^\\+/,end:/$/},{className:\"deletion\",begin:/^-/,end:/$/},{className:\"addition\",begin:/^!/,end:/$/}]}},grmr_go:e=>{const n={keyword:[\"break\",\"case\",\"chan\",\"const\",\"continue\",\"default\",\"defer\",\"else\",\"fallthrough\",\"for\",\"func\",\"go\",\"goto\",\"if\",\"import\",\"interface\",\"map\",\"package\",\"range\",\"return\",\"select\",\"struct\",\"case\",\"type\",\"var\"],type:[\"bool\",\"byte\",\"complex64\",\"complex128\",\"error\",\"float32\",\"float64\",\"int8\",\"int16\",\"int32\",\"int64\",\"string\",\"uint8\",\"uint16\",\"uint32\",\"uint64\",\"int\",\"uint\",\"uintptr\",\"rune\"],literal:[\"true\",\"false\",\"iota\",\"nil\"],built_in:[\"append\",\"cap\",\"close\",\"complex\",\"copy\",\"imag\",\"len\",\"make\",\"new\",\"panic\",\"print\",\"println\",\"real\",\"recover\",\"delete\"]};return{name:\"Go\",aliases:[\"golang\"],keywords:n,illegal:\"</\",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:\"string\",variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:\"`\",end:\"`\"}]},{className:\"number\",variants:[{begin:e.C_NUMBER_RE+\"[i]\",relevance:1},e.C_NUMBER_MODE]},{begin:/:=/},{className:\"function\",beginKeywords:\"func\",end:\"\\\\s*(\\\\{|$)\",excludeEnd:!0,contains:[e.TITLE_MODE,{className:\"params\",begin:/\\(/,end:/\\)/,endsParent:!0,keywords:n,illegal:/[\"']/}]}]}},grmr_ini:e=>{const n=e.regex,t={className:\"number\",relevance:0,variants:[{begin:/([+-]+)?[\\d]+_[\\d_]+/},{begin:e.NUMBER_RE}]},a=e.COMMENT();a.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];const i={className:\"variable\",variants:[{begin:/\\$[\\w\\d\"][\\w\\d_]*/},{begin:/\\$\\{(.*?)\\}/}]},r={className:\"literal\",begin:/\\bon|off|true|false|yes|no\\b/},s={className:\"string\",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:\"'''\",end:\"'''\",relevance:10},{begin:'\"\"\"',end:'\"\"\"',relevance:10},{begin:'\"',end:'\"'},{begin:\"'\",end:\"'\"}]},o={begin:/\\[/,end:/\\]/,contains:[a,r,i,s,t,\"self\"],relevance:0},l=n.either(/[A-Za-z0-9_-]+/,/\"(\\\\\"|[^\"])*\"/,/'[^']*'/);return{name:\"TOML, also INI\",aliases:[\"toml\"],case_insensitive:!0,illegal:/\\S/,contains:[a,{className:\"section\",begin:/\\[+/,end:/\\]+/},{begin:n.concat(l,\"(\\\\s*\\\\.\\\\s*\",l,\")*\",n.lookahead(/\\s*=\\s*[^#\\s]/)),className:\"attr\",starts:{end:/$/,contains:[a,o,r,i,s,t]}}]}},grmr_java:e=>{const n=e.regex,t=\"[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*\",a=t+ue(\"(?:<\"+t+\"~~~(?:\\\\s*,\\\\s*\"+t+\"~~~)*>)?\",/~~~/g,2),i={keyword:[\"synchronized\",\"abstract\",\"private\",\"var\",\"static\",\"if\",\"const \",\"for\",\"while\",\"strictfp\",\"finally\",\"protected\",\"import\",\"native\",\"final\",\"void\",\"enum\",\"else\",\"break\",\"transient\",\"catch\",\"instanceof\",\"volatile\",\"case\",\"assert\",\"package\",\"default\",\"public\",\"try\",\"case\",\"continue\",\"throws\",\"protected\",\"public\",\"private\",\"module\",\"requires\",\"exports\",\"do\",\"sealed\"],literal:[\"false\",\"true\",\"null\"],type:[\"char\",\"boolean\",\"long\",\"float\",\"int\",\"byte\",\"short\",\"double\"],built_in:[\"super\",\"this\"]},r={className:\"meta\",begin:\"@\"+t,contains:[{begin:/\\(/,end:/\\)/,contains:[\"self\"]}]},s={className:\"params\",begin:/\\(/,end:/\\)/,keywords:i,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE],endsParent:!0};return{name:\"Java\",aliases:[\"jsp\"],keywords:i,illegal:/<\\/|#/,contains:[e.COMMENT(\"/\\\\*\\\\*\",\"\\\\*/\",{relevance:0,contains:[{begin:/\\w+@/,relevance:0},{className:\"doctag\",begin:\"@[A-Za-z]+\"}]}),{begin:/import java\\.[a-z]+\\./,keywords:\"import\",relevance:2},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{begin:/\"\"\"/,end:/\"\"\"/,className:\"string\",contains:[e.BACKSLASH_ESCAPE]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{match:[/\\b(?:class|interface|enum|extends|implements|new)/,/\\s+/,t],className:{1:\"keyword\",3:\"title.class\"}},{match:/non-sealed/,scope:\"keyword\"},{begin:[n.concat(/(?!else)/,t),/\\s+/,t,/\\s+/,/=/],className:{1:\"type\",3:\"variable\",5:\"operator\"}},{begin:[/record/,/\\s+/,t],className:{1:\"keyword\",3:\"title.class\"},contains:[s,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:\"new throw return else\",relevance:0},{begin:[\"(?:\"+a+\"\\\\s+)\",e.UNDERSCORE_IDENT_RE,/\\s*(?=\\()/],className:{2:\"title.function\"},keywords:i,contains:[{className:\"params\",begin:/\\(/,end:/\\)/,keywords:i,relevance:0,contains:[r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,ge,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},ge,r]}},grmr_javascript:we,grmr_json:e=>({name:\"JSON\",contains:[{className:\"attr\",begin:/\"(\\\\.|[^\\\\\"\\r\\n])*\"(?=\\s*:)/,relevance:1.01},{match:/[{}[\\],:]/,className:\"punctuation\",relevance:0},e.QUOTE_STRING_MODE,{beginKeywords:\"true false null\"},e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],illegal:\"\\\\S\"}),grmr_kotlin:e=>{const n={keyword:\"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual\",built_in:\"Byte Short Char Int Long Boolean Float Double Void Unit Nothing\",literal:\"true false null\"},t={className:\"symbol\",begin:e.UNDERSCORE_IDENT_RE+\"@\"},a={className:\"subst\",begin:/\\$\\{/,end:/\\}/,contains:[e.C_NUMBER_MODE]},i={className:\"variable\",begin:\"\\\\$\"+e.UNDERSCORE_IDENT_RE},r={className:\"string\",variants:[{begin:'\"\"\"',end:'\"\"\"(?=[^\"])',contains:[i,a]},{begin:\"'\",end:\"'\",illegal:/\\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'\"',end:'\"',illegal:/\\n/,contains:[e.BACKSLASH_ESCAPE,i,a]}]};a.contains.push(r);const s={className:\"meta\",begin:\"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\\\s*:(?:\\\\s*\"+e.UNDERSCORE_IDENT_RE+\")?\"},o={className:\"meta\",begin:\"@\"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\\(/,end:/\\)/,contains:[e.inherit(r,{className:\"string\"})]}]},l=ge,c=e.COMMENT(\"/\\\\*\",\"\\\\*/\",{contains:[e.C_BLOCK_COMMENT_MODE]}),d={variants:[{className:\"type\",begin:e.UNDERSCORE_IDENT_RE},{begin:/\\(/,end:/\\)/,contains:[]}]},g=d;return g.variants[1].contains=[d],d.variants[1].contains=[g],{name:\"Kotlin\",aliases:[\"kt\",\"kts\"],keywords:n,contains:[e.COMMENT(\"/\\\\*\\\\*\",\"\\\\*/\",{relevance:0,contains:[{className:\"doctag\",begin:\"@[A-Za-z]+\"}]}),e.C_LINE_COMMENT_MODE,c,{className:\"keyword\",begin:/\\b(break|continue|return|this)\\b/,starts:{contains:[{className:\"symbol\",begin:/@\\w+/}]}},t,s,o,{className:\"function\",beginKeywords:\"fun\",end:\"[(]|$\",returnBegin:!0,excludeEnd:!0,keywords:n,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+\"\\\\s*\\\\(\",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:\"type\",begin:/</,end:/>/,keywords:\"reified\",relevance:0},{className:\"params\",begin:/\\(/,end:/\\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\\/]/,endsWithParent:!0,contains:[d,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,s,o,r,e.C_NUMBER_MODE]},c]},{className:\"class\",beginKeywords:\"class interface trait\",end:/[:\\{(]|$/,excludeEnd:!0,illegal:\"extends implements\",contains:[{beginKeywords:\"public protected internal private constructor\"},e.UNDERSCORE_TITLE_MODE,{className:\"type\",begin:/</,end:/>/,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:\"type\",begin:/[,:]\\s*/,end:/[<\\(,]|$/,excludeBegin:!0,returnEnd:!0},s,o]},r,{className:\"meta\",begin:\"^#!/usr/bin/env\",end:\"$\",illegal:\"\\n\"},l]}},grmr_less:e=>{const n=te(e),t=le,a=\"([\\\\w-]+|@\\\\{[\\\\w-]+\\\\})\",i=[],r=[],s=e=>({className:\"string\",begin:\"~?\"+e+\".*?\"+e}),o=(e,n,t)=>({className:e,begin:n,relevance:t}),l={$pattern:/[a-z-]+/,keyword:\"and or not only\",attribute:ie.join(\" \")},c={begin:\"\\\\(\",end:\"\\\\)\",contains:r,keywords:l,relevance:0};r.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s(\"'\"),s('\"'),n.CSS_NUMBER_MODE,{begin:\"(url|data-uri)\\\\(\",starts:{className:\"string\",end:\"[\\\\)\\\\n]\",excludeEnd:!0}},n.HEXCOLOR,c,o(\"variable\",\"@@?[\\\\w-]+\",10),o(\"variable\",\"@\\\\{[\\\\w-]+\\\\}\"),o(\"built_in\",\"~?`[^`]*?`\"),{className:\"attribute\",begin:\"[\\\\w-]+\\\\s*:\",end:\":\",returnBegin:!0,excludeEnd:!0},n.IMPORTANT);const d=r.concat({begin:/\\{/,end:/\\}/,contains:i}),g={beginKeywords:\"when\",endsWithParent:!0,contains:[{beginKeywords:\"and not\"}].concat(r)},u={begin:a+\"\\\\s*:\",returnBegin:!0,end:/[;}]/,relevance:0,contains:[{begin:/-(webkit|moz|ms|o)-/},n.CSS_VARIABLE,{className:\"attribute\",begin:\"\\\\b(\"+oe.join(\"|\")+\")\\\\b\",end:/(?=:)/,starts:{endsWithParent:!0,illegal:\"[<=$]\",relevance:0,contains:r}}]},b={className:\"keyword\",begin:\"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\\\b\",starts:{end:\"[;{}]\",keywords:l,returnEnd:!0,contains:r,relevance:0}},m={className:\"variable\",variants:[{begin:\"@[\\\\w-]+\\\\s*:\",relevance:15},{begin:\"@[\\\\w-]+\"}],starts:{end:\"[;}]\",returnEnd:!0,contains:d}},p={variants:[{begin:\"[\\\\.#:&\\\\[>]\",end:\"[;{}]\"},{begin:a,end:/\\{/}],returnBegin:!0,returnEnd:!0,illegal:\"[<='$\\\"]\",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,o(\"keyword\",\"all\\\\b\"),o(\"variable\",\"@\\\\{[\\\\w-]+\\\\}\"),{begin:\"\\\\b(\"+ae.join(\"|\")+\")\\\\b\",className:\"selector-tag\"},n.CSS_NUMBER_MODE,o(\"selector-tag\",a,0),o(\"selector-id\",\"#\"+a),o(\"selector-class\",\"\\\\.\"+a,0),o(\"selector-tag\",\"&\",0),n.ATTRIBUTE_SELECTOR_MODE,{className:\"selector-pseudo\",begin:\":(\"+re.join(\"|\")+\")\"},{className:\"selector-pseudo\",begin:\":(:)?(\"+se.join(\"|\")+\")\"},{begin:/\\(/,end:/\\)/,relevance:0,contains:d},{begin:\"!important\"},n.FUNCTION_DISPATCH]},_={begin:`[\\\\w-]+:(:)?(${t.join(\"|\")})`,returnBegin:!0,contains:[p]};return i.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,b,m,_,u,p),{name:\"Less\",case_insensitive:!0,illegal:\"[=>'/<($\\\"]\",contains:i}},grmr_lua:e=>{const n=\"\\\\[=*\\\\[\",t=\"\\\\]=*\\\\]\",a={begin:n,end:t,contains:[\"self\"]},i=[e.COMMENT(\"--(?!\\\\[=*\\\\[)\",\"$\"),e.COMMENT(\"--\\\\[=*\\\\[\",t,{contains:[a],relevance:10})];return{name:\"Lua\",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:\"true false nil\",keyword:\"and break do else elseif end for goto if in local not or repeat return then until while\",built_in:\"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove\"},contains:i.concat([{className:\"function\",beginKeywords:\"function\",end:\"\\\\)\",contains:[e.inherit(e.TITLE_MODE,{begin:\"([_a-zA-Z]\\\\w*\\\\.)*([_a-zA-Z]\\\\w*:)?[_a-zA-Z]\\\\w*\"}),{className:\"params\",begin:\"\\\\(\",endsWithParent:!0,contains:i}].concat(i)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:\"string\",begin:n,end:t,contains:[a],relevance:5}])}},grmr_makefile:e=>{const n={className:\"variable\",variants:[{begin:\"\\\\$\\\\(\"+e.UNDERSCORE_IDENT_RE+\"\\\\)\",contains:[e.BACKSLASH_ESCAPE]},{begin:/\\$[@%<?\\^\\+\\*]/}]},t={className:\"string\",begin:/\"/,end:/\"/,contains:[e.BACKSLASH_ESCAPE,n]},a={className:\"variable\",begin:/\\$\\([\\w-]+\\s/,end:/\\)/,keywords:{built_in:\"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value\"},contains:[n]},i={begin:\"^\"+e.UNDERSCORE_IDENT_RE+\"\\\\s*(?=[:+?]?=)\"},r={className:\"section\",begin:/^[^\\s]+:/,end:/$/,contains:[n]};return{name:\"Makefile\",aliases:[\"mk\",\"mak\",\"make\"],keywords:{$pattern:/[\\w-]+/,keyword:\"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath\"},contains:[e.HASH_COMMENT_MODE,n,t,a,i,{className:\"meta\",begin:/^\\.PHONY:/,end:/$/,keywords:{$pattern:/[\\.\\w]+/,keyword:\".PHONY\"}},r]}},grmr_xml:e=>{const n=e.regex,t=n.concat(/[A-Z_]/,n.optional(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),a={className:\"symbol\",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\\s/,contains:[{className:\"keyword\",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\\n/}]},r=e.inherit(i,{begin:/\\(/,end:/\\)/}),s=e.inherit(e.APOS_STRING_MODE,{className:\"string\"}),o=e.inherit(e.QUOTE_STRING_MODE,{className:\"string\"}),l={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:\"attr\",begin:/[A-Za-z0-9._:-]+/,relevance:0},{begin:/=\\s*/,relevance:0,contains:[{className:\"string\",endsParent:!0,variants:[{begin:/\"/,end:/\"/,contains:[a]},{begin:/'/,end:/'/,contains:[a]},{begin:/[^\\s\"'=<>`]+/}]}]}]};return{name:\"HTML, XML\",aliases:[\"html\",\"xhtml\",\"rss\",\"atom\",\"xjb\",\"xsd\",\"xsl\",\"plist\",\"wsf\",\"svg\"],case_insensitive:!0,contains:[{className:\"meta\",begin:/<![a-z]/,end:/>/,relevance:10,contains:[i,o,s,r,{begin:/\\[/,end:/\\]/,contains:[{className:\"meta\",begin:/<![a-z]/,end:/>/,contains:[i,r,o,s]}]}]},e.COMMENT(/<!--/,/-->/,{relevance:10}),{begin:/<!\\[CDATA\\[/,end:/\\]\\]>/,relevance:10},a,{className:\"meta\",end:/\\?>/,variants:[{begin:/<\\?xml/,relevance:10,contains:[o]},{begin:/<\\?[a-z][a-z0-9]+/}]},{className:\"tag\",begin:/<style(?=\\s|>)/,end:/>/,keywords:{name:\"style\"},contains:[l],starts:{end:/<\\/style>/,returnEnd:!0,subLanguage:[\"css\",\"xml\"]}},{className:\"tag\",begin:/<script(?=\\s|>)/,end:/>/,keywords:{name:\"script\"},contains:[l],starts:{end:/<\\/script>/,returnEnd:!0,subLanguage:[\"javascript\",\"handlebars\",\"xml\"]}},{className:\"tag\",begin:/<>|<\\/>/},{className:\"tag\",begin:n.concat(/</,n.lookahead(n.concat(t,n.either(/\\/>/,/>/,/\\s/)))),end:/\\/?>/,contains:[{className:\"name\",begin:t,relevance:0,starts:l}]},{className:\"tag\",begin:n.concat(/<\\//,n.lookahead(n.concat(t,/>/))),contains:[{className:\"name\",begin:t,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}},grmr_markdown:e=>{const n={begin:/<\\/?[A-Za-z_]/,end:\">\",subLanguage:\"xml\",relevance:0},t={variants:[{begin:/\\[.+?\\]\\[.*?\\]/,relevance:0},{begin:/\\[.+?\\]\\(((data|javascript|mailto):|(?:http|ftp)s?:\\/\\/).*?\\)/,relevance:2},{begin:e.regex.concat(/\\[.+?\\]\\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\\/\\/.*?\\)/),relevance:2},{begin:/\\[.+?\\]\\([./?&#].*?\\)/,relevance:1},{begin:/\\[.*?\\]\\(.*?\\)/,relevance:0}],returnBegin:!0,contains:[{match:/\\[(?=\\])/},{className:\"string\",relevance:0,begin:\"\\\\[\",end:\"\\\\]\",excludeBegin:!0,returnEnd:!0},{className:\"link\",relevance:0,begin:\"\\\\]\\\\(\",end:\"\\\\)\",excludeBegin:!0,excludeEnd:!0},{className:\"symbol\",relevance:0,begin:\"\\\\]\\\\[\",end:\"\\\\]\",excludeBegin:!0,excludeEnd:!0}]},a={className:\"strong\",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\\*{2}/,end:/\\*{2}/}]},i={className:\"emphasis\",contains:[],variants:[{begin:/\\*(?!\\*)/,end:/\\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]},r=e.inherit(a,{contains:[]}),s=e.inherit(i,{contains:[]});a.contains.push(s),i.contains.push(r);let o=[n,t];return[a,i,r,s].forEach(e=>{e.contains=e.contains.concat(o)}),o=o.concat(a,i),{name:\"Markdown\",aliases:[\"md\",\"mkdown\",\"mkd\"],contains:[{className:\"section\",variants:[{begin:\"^#{1,6}\",end:\"$\",contains:o},{begin:\"(?=^.+?\\\\n[=-]{2,}$)\",contains:[{begin:\"^[=-]*$\"},{begin:\"^\",end:\"\\\\n\",contains:o}]}]},n,{className:\"bullet\",begin:\"^[ \\t]*([*+-]|(\\\\d+\\\\.))(?=\\\\s+)\",end:\"\\\\s+\",excludeEnd:!0},a,i,{className:\"quote\",begin:\"^>\\\\s+\",contains:o,end:\"$\"},{className:\"code\",variants:[{begin:\"(`{3,})[^`](.|\\\\n)*?\\\\1`*[ ]*\"},{begin:\"(~{3,})[^~](.|\\\\n)*?\\\\1~*[ ]*\"},{begin:\"```\",end:\"```+[ ]*$\"},{begin:\"~~~\",end:\"~~~+[ ]*$\"},{begin:\"`.+?`\"},{begin:\"(?=^( {4}|\\\\t))\",contains:[{begin:\"^( {4}|\\\\t)\",end:\"(\\\\n)$\"}],relevance:0}]},{begin:\"^[-\\\\*]{3,}\",end:\"$\"},t,{begin:/^\\[[^\\n]+\\]:/,returnBegin:!0,contains:[{className:\"symbol\",begin:/\\[/,end:/\\]/,excludeBegin:!0,excludeEnd:!0},{className:\"link\",begin:/:\\s*/,end:/$/,excludeBegin:!0}]}]}},grmr_objectivec:e=>{const n=/[a-zA-Z@][a-zA-Z0-9_]*/,t={$pattern:n,keyword:[\"@interface\",\"@class\",\"@protocol\",\"@implementation\"]};return{name:\"Objective-C\",aliases:[\"mm\",\"objc\",\"obj-c\",\"obj-c++\",\"objective-c++\"],keywords:{\"variable.language\":[\"this\",\"super\"],$pattern:n,keyword:[\"while\",\"export\",\"sizeof\",\"typedef\",\"const\",\"struct\",\"for\",\"union\",\"volatile\",\"static\",\"mutable\",\"if\",\"do\",\"return\",\"goto\",\"enum\",\"else\",\"break\",\"extern\",\"asm\",\"case\",\"default\",\"register\",\"explicit\",\"typename\",\"case\",\"continue\",\"inline\",\"readonly\",\"assign\",\"readwrite\",\"self\",\"@synchronized\",\"id\",\"typeof\",\"nonatomic\",\"IBOutlet\",\"IBAction\",\"strong\",\"weak\",\"copy\",\"in\",\"out\",\"inout\",\"bycopy\",\"byref\",\"oneway\",\"__strong\",\"__weak\",\"__block\",\"__autoreleasing\",\"@private\",\"@protected\",\"@public\",\"@try\",\"@property\",\"@end\",\"@throw\",\"@catch\",\"@finally\",\"@autoreleasepool\",\"@synthesize\",\"@dynamic\",\"@selector\",\"@optional\",\"@required\",\"@encode\",\"@package\",\"@import\",\"@defs\",\"@compatibility_alias\",\"__bridge\",\"__bridge_transfer\",\"__bridge_retained\",\"__bridge_retain\",\"__covariant\",\"__contravariant\",\"__kindof\",\"_Nonnull\",\"_Nullable\",\"_Null_unspecified\",\"__FUNCTION__\",\"__PRETTY_FUNCTION__\",\"__attribute__\",\"getter\",\"setter\",\"retain\",\"unsafe_unretained\",\"nonnull\",\"nullable\",\"null_unspecified\",\"null_resettable\",\"class\",\"instancetype\",\"NS_DESIGNATED_INITIALIZER\",\"NS_UNAVAILABLE\",\"NS_REQUIRES_SUPER\",\"NS_RETURNS_INNER_POINTER\",\"NS_INLINE\",\"NS_AVAILABLE\",\"NS_DEPRECATED\",\"NS_ENUM\",\"NS_OPTIONS\",\"NS_SWIFT_UNAVAILABLE\",\"NS_ASSUME_NONNULL_BEGIN\",\"NS_ASSUME_NONNULL_END\",\"NS_REFINED_FOR_SWIFT\",\"NS_SWIFT_NAME\",\"NS_SWIFT_NOTHROW\",\"NS_DURING\",\"NS_HANDLER\",\"NS_ENDHANDLER\",\"NS_VALUERETURN\",\"NS_VOIDRETURN\"],literal:[\"false\",\"true\",\"FALSE\",\"TRUE\",\"nil\",\"YES\",\"NO\",\"NULL\"],built_in:[\"dispatch_once_t\",\"dispatch_queue_t\",\"dispatch_sync\",\"dispatch_async\",\"dispatch_once\"],type:[\"int\",\"float\",\"char\",\"unsigned\",\"signed\",\"short\",\"long\",\"double\",\"wchar_t\",\"unichar\",\"void\",\"bool\",\"BOOL\",\"id|0\",\"_Bool\"]},illegal:\"</\",contains:[{className:\"built_in\",begin:\"\\\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\\\w+\"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:\"string\",variants:[{begin:'@\"',end:'\"',illegal:\"\\\\n\",contains:[e.BACKSLASH_ESCAPE]}]},{className:\"meta\",begin:/#\\s*[a-z]+\\b/,end:/$/,keywords:{keyword:\"if else elif endif define undef warning error line pragma ifdef ifndef include\"},contains:[{begin:/\\\\\\n/,relevance:0},e.inherit(e.QUOTE_STRING_MODE,{className:\"string\"}),{className:\"string\",begin:/<.*?>/,end:/$/,illegal:\"\\\\n\"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:\"class\",begin:\"(\"+t.keyword.join(\"|\")+\")\\\\b\",end:/(\\{|$)/,excludeEnd:!0,keywords:t,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:\"\\\\.\"+e.UNDERSCORE_IDENT_RE,relevance:0}]}},grmr_perl:e=>{const n=e.regex,t=/[dualxmsipngr]{0,12}/,a={$pattern:/[\\w.]+/,keyword:\"abs accept alarm and atan2 bind binmode bless break caller chdir chmod chomp chop chown chr chroot close closedir connect continue cos crypt dbmclose dbmopen defined delete die do dump each else elsif endgrent endhostent endnetent endprotoent endpwent endservent eof eval exec exists exit exp fcntl fileno flock for foreach fork format formline getc getgrent getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr getnetbyname getnetent getpeername getpgrp getpriority getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid getservbyname getservbyport getservent getsockname getsockopt given glob gmtime goto grep gt hex if index int ioctl join keys kill last lc lcfirst length link listen local localtime log lstat lt ma map mkdir msgctl msgget msgrcv msgsnd my ne next no not oct open opendir or ord our pack package pipe pop pos print printf prototype push q|0 qq quotemeta qw qx rand read readdir readline readlink readpipe recv redo ref rename require reset return reverse rewinddir rindex rmdir say scalar seek seekdir select semctl semget semop send setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent setservent setsockopt shift shmctl shmget shmread shmwrite shutdown sin sleep socket socketpair sort splice split sprintf sqrt srand stat state study sub substr symlink syscall sysopen sysread sysseek system syswrite tell telldir tie tied time times tr truncate uc ucfirst umask undef unless unlink unpack unshift untie until use utime values vec wait waitpid wantarray warn when while write x|0 xor y|0\"},i={className:\"subst\",begin:\"[$@]\\\\{\",end:\"\\\\}\",keywords:a},r={begin:/->\\{/,end:/\\}/},s={variants:[{begin:/\\$\\d/},{begin:n.concat(/[$%@](\\^\\w\\b|#\\w+(::\\w+)*|\\{\\w+\\}|\\w+(::\\w*)*)/,\"(?![A-Za-z])(?![@$%])\")},{begin:/[$%@][^\\s\\w{]/,relevance:0}]},o=[e.BACKSLASH_ESCAPE,i,s],l=[/!/,/\\//,/\\|/,/\\?/,/'/,/\"/,/#/],c=(e,a,i=\"\\\\1\")=>{const r=\"\\\\1\"===i?i:n.concat(i,a);return n.concat(n.concat(\"(?:\",e,\")\"),a,/(?:\\\\.|[^\\\\\\/])*?/,r,/(?:\\\\.|[^\\\\\\/])*?/,i,t)},d=(e,a,i)=>n.concat(n.concat(\"(?:\",e,\")\"),a,/(?:\\\\.|[^\\\\\\/])*?/,i,t),g=[s,e.HASH_COMMENT_MODE,e.COMMENT(/^=\\w/,/=cut/,{endsWithParent:!0}),r,{className:\"string\",contains:o,variants:[{begin:\"q[qwxr]?\\\\s*\\\\(\",end:\"\\\\)\",relevance:5},{begin:\"q[qwxr]?\\\\s*\\\\[\",end:\"\\\\]\",relevance:5},{begin:\"q[qwxr]?\\\\s*\\\\{\",end:\"\\\\}\",relevance:5},{begin:\"q[qwxr]?\\\\s*\\\\|\",end:\"\\\\|\",relevance:5},{begin:\"q[qwxr]?\\\\s*<\",end:\">\",relevance:5},{begin:\"qw\\\\s+q\",end:\"q\",relevance:5},{begin:\"'\",end:\"'\",contains:[e.BACKSLASH_ESCAPE]},{begin:'\"',end:'\"'},{begin:\"`\",end:\"`\",contains:[e.BACKSLASH_ESCAPE]},{begin:/\\{\\w+\\}/,relevance:0},{begin:\"-?\\\\w+\\\\s*=>\",relevance:0}]},{className:\"number\",begin:\"(\\\\b0[0-7_]+)|(\\\\b0x[0-9a-fA-F_]+)|(\\\\b[1-9][0-9_]*(\\\\.[0-9_]+)?)|[0_]\\\\b\",relevance:0},{begin:\"(\\\\/\\\\/|\"+e.RE_STARTERS_RE+\"|\\\\b(split|return|print|reverse|grep)\\\\b)\\\\s*\",keywords:\"split return print reverse grep\",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:\"regexp\",variants:[{begin:c(\"s|tr|y\",n.either(...l,{capture:!0}))},{begin:c(\"s|tr|y\",\"\\\\(\",\"\\\\)\")},{begin:c(\"s|tr|y\",\"\\\\[\",\"\\\\]\")},{begin:c(\"s|tr|y\",\"\\\\{\",\"\\\\}\")}],relevance:2},{className:\"regexp\",variants:[{begin:/(m|qr)\\/\\//,relevance:0},{begin:d(\"(?:m|qr)?\",/\\//,/\\//)},{begin:d(\"m|qr\",n.either(...l,{capture:!0}),/\\1/)},{begin:d(\"m|qr\",/\\(/,/\\)/)},{begin:d(\"m|qr\",/\\[/,/\\]/)},{begin:d(\"m|qr\",/\\{/,/\\}/)}]}]},{className:\"function\",beginKeywords:\"sub\",end:\"(\\\\s*\\\\(.*?\\\\))?[;{]\",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:\"-\\\\w\\\\b\",relevance:0},{begin:\"^__DATA__$\",end:\"^__END__$\",subLanguage:\"mojolicious\",contains:[{begin:\"^@@.*\",end:\"$\",className:\"comment\"}]}];return i.contains=g,r.contains=g,{name:\"Perl\",aliases:[\"pl\",\"pm\"],keywords:a,contains:g}},grmr_php:e=>{const n=e.regex,t=/(?![A-Za-z0-9])(?![$])/,a=n.concat(/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/,t),i=n.concat(/(\\\\?[A-Z][a-z0-9_\\x7f-\\xff]+|\\\\?[A-Z]+(?=[A-Z][a-z0-9_\\x7f-\\xff])){1,}/,t),r={scope:\"variable\",match:\"\\\\$+\"+a},s={scope:\"subst\",variants:[{begin:/\\$\\w+/},{begin:/\\{\\$/,end:/\\}/}]},o=e.inherit(e.APOS_STRING_MODE,{illegal:null}),l=\"[ \\t\\n]\",c={scope:\"string\",variants:[e.inherit(e.QUOTE_STRING_MODE,{illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(s)}),o,e.END_SAME_AS_BEGIN({begin:/<<<[ \\t]*(\\w+)\\n/,end:/[ \\t]*(\\w+)\\b/,contains:e.QUOTE_STRING_MODE.contains.concat(s)})]},d={scope:\"number\",variants:[{begin:\"\\\\b0[bB][01]+(?:_[01]+)*\\\\b\"},{begin:\"\\\\b0[oO][0-7]+(?:_[0-7]+)*\\\\b\"},{begin:\"\\\\b0[xX][\\\\da-fA-F]+(?:_[\\\\da-fA-F]+)*\\\\b\"},{begin:\"(?:\\\\b\\\\d+(?:_\\\\d+)*(\\\\.(?:\\\\d+(?:_\\\\d+)*))?|\\\\B\\\\.\\\\d+)(?:[eE][+-]?\\\\d+)?\"}],relevance:0},g=[\"false\",\"null\",\"true\"],u=[\"__CLASS__\",\"__DIR__\",\"__FILE__\",\"__FUNCTION__\",\"__COMPILER_HALT_OFFSET__\",\"__LINE__\",\"__METHOD__\",\"__NAMESPACE__\",\"__TRAIT__\",\"die\",\"echo\",\"exit\",\"include\",\"include_once\",\"print\",\"require\",\"require_once\",\"array\",\"abstract\",\"and\",\"as\",\"binary\",\"bool\",\"boolean\",\"break\",\"callable\",\"case\",\"catch\",\"class\",\"clone\",\"const\",\"continue\",\"declare\",\"default\",\"do\",\"double\",\"else\",\"elseif\",\"empty\",\"enddeclare\",\"endfor\",\"endforeach\",\"endif\",\"endcase\",\"endwhile\",\"enum\",\"eval\",\"extends\",\"final\",\"finally\",\"float\",\"for\",\"foreach\",\"from\",\"global\",\"goto\",\"if\",\"implements\",\"instanceof\",\"insteadof\",\"int\",\"integer\",\"interface\",\"isset\",\"iterable\",\"list\",\"match|0\",\"mixed\",\"new\",\"never\",\"object\",\"or\",\"private\",\"protected\",\"public\",\"readonly\",\"real\",\"return\",\"string\",\"case\",\"throw\",\"trait\",\"try\",\"unset\",\"use\",\"var\",\"void\",\"while\",\"xor\",\"yield\"],b=[\"Error|0\",\"AppendIterator\",\"ArgumentCountError\",\"ArithmeticError\",\"ArrayIterator\",\"ArrayObject\",\"AssertionError\",\"BadFunctionCallException\",\"BadMethodCallException\",\"CachingIterator\",\"CallbackFilterIterator\",\"CompileError\",\"Countable\",\"DirectoryIterator\",\"DivisionByZeroError\",\"DomainException\",\"EmptyIterator\",\"ErrorException\",\"Exception\",\"FilesystemIterator\",\"FilterIterator\",\"GlobIterator\",\"InfiniteIterator\",\"InvalidArgumentException\",\"IteratorIterator\",\"LengthException\",\"LimitIterator\",\"LogicException\",\"MultipleIterator\",\"NoRewindIterator\",\"OutOfBoundsException\",\"OutOfRangeException\",\"OuterIterator\",\"OverflowException\",\"ParentIterator\",\"ParseError\",\"RangeException\",\"RecursiveArrayIterator\",\"RecursiveCachingIterator\",\"RecursiveCallbackFilterIterator\",\"RecursiveDirectoryIterator\",\"RecursiveFilterIterator\",\"RecursiveIterator\",\"RecursiveIteratorIterator\",\"RecursiveRegexIterator\",\"RecursiveTreeIterator\",\"RegexIterator\",\"RuntimeException\",\"SeekableIterator\",\"SplDoublyLinkedList\",\"SplFileInfo\",\"SplFileObject\",\"SplFixedArray\",\"SplHeap\",\"SplMaxHeap\",\"SplMinHeap\",\"SplObjectStorage\",\"SplObserver\",\"SplPriorityQueue\",\"SplQueue\",\"SplStack\",\"SplSubject\",\"SplTempFileObject\",\"TypeError\",\"UnderflowException\",\"UnexpectedValueException\",\"UnhandledMatchError\",\"ArrayAccess\",\"BackedEnum\",\"Closure\",\"Fiber\",\"Generator\",\"Iterator\",\"IteratorAggregate\",\"Serializable\",\"Stringable\",\"Throwable\",\"Traversable\",\"UnitEnum\",\"WeakReference\",\"WeakMap\",\"Directory\",\"__PHP_Incomplete_Class\",\"parent\",\"php_user_filter\",\"self\",\"static\",\"stdClass\"],m={keyword:u,literal:(e=>{const n=[];return e.forEach(e=>{n.push(e),e.toLowerCase()===e?n.push(e.toUpperCase()):n.push(e.toLowerCase())}),n})(g),built_in:b},p=e=>e.map(e=>e.replace(/\\|\\d+$/,\"\")),_={variants:[{match:[/new/,n.concat(l,\"+\"),n.concat(\"(?!\",p(b).join(\"\\\\b|\"),\"\\\\b)\"),i],scope:{1:\"keyword\",4:\"title.class\"}}]},h=n.concat(a,\"\\\\b(?!\\\\()\"),f={variants:[{match:[n.concat(/::/,n.lookahead(/(?!class\\b)/)),h],scope:{2:\"variable.constant\"}},{match:[/::/,/class/],scope:{2:\"variable.language\"}},{match:[i,n.concat(/::/,n.lookahead(/(?!class\\b)/)),h],scope:{1:\"title.class\",3:\"variable.constant\"}},{match:[i,n.concat(\"::\",n.lookahead(/(?!class\\b)/))],scope:{1:\"title.class\"}},{match:[i,/::/,/class/],scope:{1:\"title.class\",3:\"variable.language\"}}]},E={scope:\"attr\",match:n.concat(a,n.lookahead(\":\"),n.lookahead(/(?!::)/))},y={relevance:0,begin:/\\(/,end:/\\)/,keywords:m,contains:[E,r,f,e.C_BLOCK_COMMENT_MODE,c,d,_]},w={relevance:0,match:[/\\b/,n.concat(\"(?!fn\\\\b|function\\\\b|\",p(u).join(\"\\\\b|\"),\"|\",p(b).join(\"\\\\b|\"),\"\\\\b)\"),a,n.concat(l,\"*\"),n.lookahead(/(?=\\()/)],scope:{3:\"title.function.invoke\"},contains:[y]};y.contains.push(w);const N=[E,f,e.C_BLOCK_COMMENT_MODE,c,d,_];return{case_insensitive:!1,keywords:m,contains:[{begin:n.concat(/#\\[\\s*/,i),beginScope:\"meta\",end:/]/,endScope:\"meta\",keywords:{literal:g,keyword:[\"new\",\"array\"]},contains:[{begin:/\\[/,end:/]/,keywords:{literal:g,keyword:[\"new\",\"array\"]},contains:[\"self\",...N]},...N,{scope:\"meta\",match:i}]},e.HASH_COMMENT_MODE,e.COMMENT(\"//\",\"$\"),e.COMMENT(\"/\\\\*\",\"\\\\*/\",{contains:[{scope:\"doctag\",match:\"@[A-Za-z]+\"}]}),{match:/__halt_compiler\\(\\);/,keywords:\"__halt_compiler\",starts:{scope:\"comment\",end:e.MATCH_NOTHING_RE,contains:[{match:/\\?>/,scope:\"meta\",endsParent:!0}]}},{scope:\"meta\",variants:[{begin:/<\\?php/,relevance:10},{begin:/<\\?=/},{begin:/<\\?/,relevance:.1},{begin:/\\?>/}]},{scope:\"variable.language\",match:/\\$this\\b/},r,w,f,{match:[/const/,/\\s/,a],scope:{1:\"keyword\",3:\"variable.constant\"}},_,{scope:\"function\",relevance:0,beginKeywords:\"fn function\",end:/[;{]/,excludeEnd:!0,illegal:\"[$%\\\\[]\",contains:[{beginKeywords:\"use\"},e.UNDERSCORE_TITLE_MODE,{begin:\"=>\",endsParent:!0},{scope:\"params\",begin:\"\\\\(\",end:\"\\\\)\",excludeBegin:!0,excludeEnd:!0,keywords:m,contains:[\"self\",r,f,e.C_BLOCK_COMMENT_MODE,c,d]}]},{scope:\"class\",variants:[{beginKeywords:\"enum\",illegal:/[($\"]/},{beginKeywords:\"class interface trait\",illegal:/[:($\"]/}],relevance:0,end:/\\{/,excludeEnd:!0,contains:[{beginKeywords:\"extends implements\"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:\"namespace\",relevance:0,end:\";\",illegal:/[.']/,contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:\"title.class\"})]},{beginKeywords:\"use\",relevance:0,end:\";\",contains:[{match:/\\b(as|const|function)\\b/,scope:\"keyword\"},e.UNDERSCORE_TITLE_MODE]},c,d]}},grmr_php_template:e=>({name:\"PHP template\",subLanguage:\"xml\",contains:[{begin:/<\\?(php|=)?/,end:/\\?>/,subLanguage:\"php\",contains:[{begin:\"/\\\\*\",end:\"\\\\*/\",skip:!0},{begin:'b\"',end:'\"',skip:!0},{begin:\"b'\",end:\"'\",skip:!0},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}),grmr_plaintext:e=>({name:\"Plain text\",aliases:[\"text\",\"txt\"],disableAutodetect:!0}),grmr_python:e=>{const n=e.regex,t=/[\\p{XID_Start}_]\\p{XID_Continue}*/u,a=[\"and\",\"as\",\"assert\",\"async\",\"await\",\"break\",\"class\",\"continue\",\"def\",\"del\",\"elif\",\"else\",\"except\",\"finally\",\"for\",\"from\",\"global\",\"if\",\"import\",\"in\",\"is\",\"lambda\",\"nonlocal|10\",\"not\",\"or\",\"pass\",\"raise\",\"return\",\"try\",\"while\",\"with\",\"yield\"],i={$pattern:/[A-Za-z]\\w+|__\\w+__/,keyword:a,built_in:[\"__import__\",\"abs\",\"all\",\"any\",\"ascii\",\"bin\",\"bool\",\"breakpoint\",\"bytearray\",\"bytes\",\"callable\",\"chr\",\"classmethod\",\"compile\",\"complex\",\"delattr\",\"dict\",\"dir\",\"divmod\",\"enumerate\",\"eval\",\"exec\",\"filter\",\"float\",\"format\",\"frozenset\",\"getattr\",\"globals\",\"hasattr\",\"hash\",\"help\",\"hex\",\"id\",\"input\",\"int\",\"isinstance\",\"issubclass\",\"iter\",\"len\",\"list\",\"locals\",\"map\",\"max\",\"memoryview\",\"min\",\"next\",\"object\",\"oct\",\"open\",\"ord\",\"pow\",\"print\",\"property\",\"range\",\"repr\",\"reversed\",\"round\",\"set\",\"setattr\",\"slice\",\"sorted\",\"staticmethod\",\"str\",\"sum\",\"super\",\"tuple\",\"type\",\"vars\",\"zip\"],literal:[\"__debug__\",\"Ellipsis\",\"False\",\"None\",\"NotImplemented\",\"True\"],type:[\"Any\",\"Callable\",\"Coroutine\",\"Dict\",\"List\",\"Literal\",\"Generic\",\"Optional\",\"Sequence\",\"Set\",\"Tuple\",\"Type\",\"Union\"]},r={className:\"meta\",begin:/^(>>>|\\.\\.\\.) /},s={className:\"subst\",begin:/\\{/,end:/\\}/,keywords:i,illegal:/#/},o={begin:/\\{\\{/,relevance:0},l={className:\"string\",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?\"\"\"/,end:/\"\"\"/,contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])\"\"\"/,end:/\"\"\"/,contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([uU]|[rR])'/,end:/'/,relevance:10},{begin:/([uU]|[rR])\"/,end:/\"/,relevance:10},{begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])\"/,end:/\"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])\"/,end:/\"/,contains:[e.BACKSLASH_ESCAPE,o,s]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},c=\"[0-9](_?[0-9])*\",d=`(\\\\b(${c}))?\\\\.(${c})|\\\\b(${c})\\\\.`,g=\"\\\\b|\"+a.join(\"|\"),u={className:\"number\",relevance:0,variants:[{begin:`(\\\\b(${c})|(${d}))[eE][+-]?(${c})[jJ]?(?=${g})`},{begin:`(${d})[jJ]?`},{begin:`\\\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${g})`},{begin:`\\\\b0[bB](_?[01])+[lL]?(?=${g})`},{begin:`\\\\b0[oO](_?[0-7])+[lL]?(?=${g})`},{begin:`\\\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${g})`},{begin:`\\\\b(${c})[jJ](?=${g})`}]},b={className:\"comment\",begin:n.lookahead(/# type:/),end:/$/,keywords:i,contains:[{begin:/# type:/},{begin:/#/,end:/\\b\\B/,endsWithParent:!0}]},m={className:\"params\",variants:[{className:\"\",begin:/\\(\\s*\\)/,skip:!0},{begin:/\\(/,end:/\\)/,excludeBegin:!0,excludeEnd:!0,keywords:i,contains:[\"self\",r,u,l,e.HASH_COMMENT_MODE]}]};return s.contains=[l,u,r],{name:\"Python\",aliases:[\"py\",\"gyp\",\"ipython\"],unicodeRegex:!0,keywords:i,illegal:/(<\\/|->|\\?)|=>/,contains:[r,u,{begin:/\\bself\\b/},{beginKeywords:\"if\",relevance:0},l,b,e.HASH_COMMENT_MODE,{match:[/\\bdef/,/\\s+/,t],scope:{1:\"keyword\",3:\"title.function\"},contains:[m]},{variants:[{match:[/\\bclass/,/\\s+/,t,/\\s*/,/\\(\\s*/,t,/\\s*\\)/]},{match:[/\\bclass/,/\\s+/,t]}],scope:{1:\"keyword\",3:\"title.class\",6:\"title.class.inherited\"}},{className:\"meta\",begin:/^[\\t ]*@/,end:/(?=#)|$/,contains:[u,m,l]}]}},grmr_python_repl:e=>({aliases:[\"pycon\"],contains:[{className:\"meta.prompt\",starts:{end:/ |$/,starts:{end:\"$\",subLanguage:\"python\"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\\.\\.\\.(?=[ ]|$)/}]}]}),grmr_r:e=>{const n=e.regex,t=/(?:(?:[a-zA-Z]|\\.[._a-zA-Z])[._a-zA-Z0-9]*)|\\.(?!\\d)/,a=n.either(/0[xX][0-9a-fA-F]+\\.[0-9a-fA-F]*[pP][+-]?\\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\\d+)?[Li]?/,/(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?[Li]?/),i=/[=!<>:]=|\\|\\||&&|:::?|<-|<<-|->>|->|\\|>|[-+*\\/?!$&|:<=>@^~]|\\*\\*/,r=n.either(/[()]/,/[{}]/,/\\[\\[/,/[[\\]]/,/\\\\/,/,/);return{name:\"R\",keywords:{$pattern:t,keyword:\"function if in break next repeat else for while\",literal:\"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10\",built_in:\"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum case tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm\"},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:\"doctag\",match:/@examples/,starts:{end:n.lookahead(n.either(/\\n^#'\\s*(?=@[a-zA-Z]+)/,/\\n^(?!#')/)),endsParent:!0}},{scope:\"doctag\",begin:\"@param\",end:/$/,contains:[{scope:\"variable\",variants:[{match:t},{match:/`(?:\\\\.|[^`\\\\])+`/}],endsParent:!0}]},{scope:\"doctag\",match:/@[a-zA-Z]+/},{scope:\"keyword\",match:/\\\\[a-zA-Z]+/}]}),e.HASH_COMMENT_MODE,{scope:\"string\",contains:[e.BACKSLASH_ESCAPE],variants:[e.END_SAME_AS_BEGIN({begin:/[rR]\"(-*)\\(/,end:/\\)(-*)\"/}),e.END_SAME_AS_BEGIN({begin:/[rR]\"(-*)\\{/,end:/\\}(-*)\"/}),e.END_SAME_AS_BEGIN({begin:/[rR]\"(-*)\\[/,end:/\\](-*)\"/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\\(/,end:/\\)(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\\{/,end:/\\}(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\\[/,end:/\\](-*)'/}),{begin:'\"',end:'\"',relevance:0},{begin:\"'\",end:\"'\",relevance:0}]},{relevance:0,variants:[{scope:{1:\"operator\",2:\"number\"},match:[i,a]},{scope:{1:\"operator\",2:\"number\"},match:[/%[^%]*%/,a]},{scope:{1:\"punctuation\",2:\"number\"},match:[r,a]},{scope:{2:\"number\"},match:[/[^a-zA-Z0-9._]|^/,a]}]},{scope:{3:\"operator\"},match:[t,/\\s+/,/<-/,/\\s+/]},{scope:\"operator\",relevance:0,variants:[{match:i},{match:/%[^%]*%/}]},{scope:\"punctuation\",relevance:0,match:r},{begin:\"`\",end:\"`\",contains:[{begin:/\\\\./}]}]}},grmr_ruby:e=>{const n=e.regex,t=\"([a-zA-Z_]\\\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\\\*\\\\*|[-/+%^&*~`|]|\\\\[\\\\]=?)\",a=n.either(/\\b([A-Z]+[a-z0-9]+)+/,/\\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=n.concat(a,/(::\\w+)*/),r={\"variable.constant\":[\"__FILE__\",\"__LINE__\"],\"variable.language\":[\"self\",\"super\"],keyword:[\"alias\",\"and\",\"attr_accessor\",\"attr_reader\",\"attr_writer\",\"begin\",\"BEGIN\",\"break\",\"case\",\"class\",\"defined\",\"do\",\"else\",\"elsif\",\"end\",\"END\",\"ensure\",\"for\",\"if\",\"in\",\"include\",\"module\",\"next\",\"not\",\"or\",\"redo\",\"require\",\"rescue\",\"retry\",\"return\",\"then\",\"undef\",\"unless\",\"until\",\"when\",\"while\",\"yield\"],built_in:[\"proc\",\"lambda\"],literal:[\"true\",\"false\",\"nil\"]},s={className:\"doctag\",begin:\"@[A-Za-z]+\"},o={begin:\"#<\",end:\">\"},l=[e.COMMENT(\"#\",\"$\",{contains:[s]}),e.COMMENT(\"^=begin\",\"^=end\",{contains:[s],relevance:10}),e.COMMENT(\"^__END__\",e.MATCH_NOTHING_RE)],c={className:\"subst\",begin:/#\\{/,end:/\\}/,keywords:r},d={className:\"string\",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/\"/,end:/\"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\\(/,end:/\\)/},{begin:/%[qQwWx]?\\[/,end:/\\]/},{begin:/%[qQwWx]?\\{/,end:/\\}/},{begin:/%[qQwWx]?</,end:/>/},{begin:/%[qQwWx]?\\//,end:/\\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\\|/,end:/\\|/},{begin:/\\B\\?(\\\\\\d{1,3})/},{begin:/\\B\\?(\\\\x[A-Fa-f0-9]{1,2})/},{begin:/\\B\\?(\\\\u\\{?[A-Fa-f0-9]{1,6}\\}?)/},{begin:/\\B\\?(\\\\M-\\\\C-|\\\\M-\\\\c|\\\\c\\\\M-|\\\\M-|\\\\C-\\\\M-)[\\x20-\\x7e]/},{begin:/\\B\\?\\\\(c|C-)[\\x20-\\x7e]/},{begin:/\\B\\?\\\\?\\S/},{begin:n.concat(/<<[-~]?'?/,n.lookahead(/(\\w+)(?=\\W)[^\\n]*\\n(?:[^\\n]*\\n)*?\\s*\\1\\b/)),contains:[e.END_SAME_AS_BEGIN({begin:/(\\w+)/,end:/(\\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},g=\"[0-9](_?[0-9])*\",u={className:\"number\",relevance:0,variants:[{begin:`\\\\b([1-9](_?[0-9])*|0)(\\\\.(${g}))?([eE][+-]?(${g})|r)?i?\\\\b`},{begin:\"\\\\b0[dD][0-9](_?[0-9])*r?i?\\\\b\"},{begin:\"\\\\b0[bB][0-1](_?[0-1])*r?i?\\\\b\"},{begin:\"\\\\b0[oO][0-7](_?[0-7])*r?i?\\\\b\"},{begin:\"\\\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\\\b\"},{begin:\"\\\\b0(_?[0-7])+r?i?\\\\b\"}]},b={variants:[{match:/\\(\\)/},{className:\"params\",begin:/\\(/,end:/(?=\\))/,excludeBegin:!0,endsParent:!0,keywords:r}]},m=[d,{variants:[{match:[/class\\s+/,i,/\\s+<\\s+/,i]},{match:[/class\\s+/,i]}],scope:{2:\"title.class\",4:\"title.class.inherited\"},keywords:r},{relevance:0,match:[i,/\\.new[ (]/],scope:{1:\"title.class\"}},{relevance:0,match:/\\b[A-Z][A-Z_0-9]+\\b/,className:\"variable.constant\"},{match:[/def/,/\\s+/,t],scope:{1:\"keyword\",3:\"title.function\"},contains:[b]},{begin:e.IDENT_RE+\"::\"},{className:\"symbol\",begin:e.UNDERSCORE_IDENT_RE+\"(!|\\\\?)?:\",relevance:0},{className:\"symbol\",begin:\":(?!\\\\s)\",contains:[d,{begin:t}],relevance:0},u,{className:\"variable\",begin:\"(\\\\$\\\\W)|((\\\\$|@@?)(\\\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])\"},{className:\"params\",begin:/\\|/,end:/\\|/,excludeBegin:!0,excludeEnd:!0,relevance:0,keywords:r},{begin:\"(\"+e.RE_STARTERS_RE+\"|unless)\\\\s*\",keywords:\"unless\",contains:[{className:\"regexp\",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\\n/,variants:[{begin:\"/\",end:\"/[a-z]*\"},{begin:/%r\\{/,end:/\\}[a-z]*/},{begin:\"%r\\\\(\",end:\"\\\\)[a-z]*\"},{begin:\"%r!\",end:\"![a-z]*\"},{begin:\"%r\\\\[\",end:\"\\\\][a-z]*\"}]}].concat(o,l),relevance:0}].concat(o,l);c.contains=m,b.contains=m;const p=[{begin:/^\\s*=>/,starts:{end:\"$\",contains:m}},{className:\"meta.prompt\",begin:\"^([>?]>|[\\\\w#]+\\\\(\\\\w+\\\\):\\\\d+:\\\\d+[>*]|(\\\\w+-)?\\\\d+\\\\.\\\\d+\\\\.\\\\d+(p\\\\d+)?[^\\\\d][^>]+>)(?=[ ])\",starts:{end:\"$\",keywords:r,contains:m}}];return l.unshift(o),{name:\"Ruby\",aliases:[\"rb\",\"gemspec\",\"podspec\",\"thor\",\"irb\"],keywords:r,illegal:/\\/\\*/,contains:[e.SHEBANG({binary:\"ruby\"})].concat(p).concat(l).concat(m)}},grmr_rust:e=>{const n=e.regex,t={className:\"title.function.invoke\",relevance:0,begin:n.concat(/\\b/,/(?!let\\b)/,e.IDENT_RE,n.lookahead(/\\s*\\(/))},a=\"([ui](8|16|32|64|128|size)|f(32|64))?\",i=[\"drop \",\"Copy\",\"Send\",\"Sized\",\"Sync\",\"Drop\",\"Fn\",\"FnMut\",\"FnOnce\",\"ToOwned\",\"Clone\",\"Debug\",\"PartialEq\",\"PartialOrd\",\"Eq\",\"Ord\",\"AsRef\",\"AsMut\",\"Into\",\"From\",\"Default\",\"Iterator\",\"Extend\",\"IntoIterator\",\"DoubleEndedIterator\",\"ExactSizeIterator\",\"SliceConcatExt\",\"ToString\",\"assert!\",\"assert_eq!\",\"bitflags!\",\"bytes!\",\"cfg!\",\"col!\",\"concat!\",\"concat_idents!\",\"debug_assert!\",\"debug_assert_eq!\",\"env!\",\"panic!\",\"file!\",\"format!\",\"format_args!\",\"include_bin!\",\"include_str!\",\"line!\",\"local_data_key!\",\"module_path!\",\"option_env!\",\"print!\",\"println!\",\"select!\",\"stringify!\",\"try!\",\"unimplemented!\",\"unreachable!\",\"vec!\",\"write!\",\"writeln!\",\"macro_rules!\",\"assert_ne!\",\"debug_assert_ne!\"];return{name:\"Rust\",aliases:[\"rs\"],keywords:{$pattern:e.IDENT_RE+\"!?\",type:[\"i8\",\"i16\",\"i32\",\"i64\",\"i128\",\"isize\",\"u8\",\"u16\",\"u32\",\"u64\",\"u128\",\"usize\",\"f32\",\"f64\",\"str\",\"char\",\"bool\",\"Box\",\"Option\",\"Result\",\"String\",\"Vec\"],keyword:[\"abstract\",\"as\",\"async\",\"await\",\"become\",\"box\",\"break\",\"const\",\"continue\",\"crate\",\"do\",\"dyn\",\"else\",\"enum\",\"extern\",\"false\",\"final\",\"fn\",\"for\",\"if\",\"impl\",\"in\",\"let\",\"loop\",\"macro\",\"match\",\"mod\",\"move\",\"mut\",\"override\",\"priv\",\"pub\",\"ref\",\"return\",\"self\",\"Self\",\"static\",\"struct\",\"super\",\"trait\",\"true\",\"try\",\"type\",\"typeof\",\"unsafe\",\"unsized\",\"use\",\"virtual\",\"where\",\"while\",\"yield\"],literal:[\"true\",\"false\",\"Some\",\"None\",\"Ok\",\"Err\"],built_in:i},illegal:\"</\",contains:[e.C_LINE_COMMENT_MODE,e.COMMENT(\"/\\\\*\",\"\\\\*/\",{contains:[\"self\"]}),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?\"/,illegal:null}),{className:\"string\",variants:[{begin:/b?r(#*)\"(.|\\n)*?\"\\1(?!#)/},{begin:/b?'\\\\?(x\\w{2}|u\\w{4}|U\\w{8}|.)'/}]},{className:\"symbol\",begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:\"number\",variants:[{begin:\"\\\\b0b([01_]+)\"+a},{begin:\"\\\\b0o([0-7_]+)\"+a},{begin:\"\\\\b0x([A-Fa-f0-9_]+)\"+a},{begin:\"\\\\b(\\\\d[\\\\d_]*(\\\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)\"+a}],relevance:0},{begin:[/fn/,/\\s+/,e.UNDERSCORE_IDENT_RE],className:{1:\"keyword\",3:\"title.function\"}},{className:\"meta\",begin:\"#!?\\\\[\",end:\"\\\\]\",contains:[{className:\"string\",begin:/\"/,end:/\"/}]},{begin:[/let/,/\\s+/,/(?:mut\\s+)?/,e.UNDERSCORE_IDENT_RE],className:{1:\"keyword\",3:\"keyword\",4:\"variable\"}},{begin:[/for/,/\\s+/,e.UNDERSCORE_IDENT_RE,/\\s+/,/in/],className:{1:\"keyword\",3:\"variable\",5:\"keyword\"}},{begin:[/type/,/\\s+/,e.UNDERSCORE_IDENT_RE],className:{1:\"keyword\",3:\"title.class\"}},{begin:[/(?:trait|enum|struct|union|impl|for)/,/\\s+/,e.UNDERSCORE_IDENT_RE],className:{1:\"keyword\",3:\"title.class\"}},{begin:e.IDENT_RE+\"::\",keywords:{keyword:\"Self\",built_in:i}},{className:\"punctuation\",begin:\"->\"},t]}},grmr_scss:e=>{const n=te(e),t=se,a=re,i=\"@[a-z-]+\",r={className:\"variable\",begin:\"(\\\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\\\b\",relevance:0};return{name:\"SCSS\",case_insensitive:!0,illegal:\"[=/|']\",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,n.CSS_NUMBER_MODE,{className:\"selector-id\",begin:\"#[A-Za-z0-9_-]+\",relevance:0},{className:\"selector-class\",begin:\"\\\\.[A-Za-z0-9_-]+\",relevance:0},n.ATTRIBUTE_SELECTOR_MODE,{className:\"selector-tag\",begin:\"\\\\b(\"+ae.join(\"|\")+\")\\\\b\",relevance:0},{className:\"selector-pseudo\",begin:\":(\"+a.join(\"|\")+\")\"},{className:\"selector-pseudo\",begin:\":(:)?(\"+t.join(\"|\")+\")\"},r,{begin:/\\(/,end:/\\)/,contains:[n.CSS_NUMBER_MODE]},n.CSS_VARIABLE,{className:\"attribute\",begin:\"\\\\b(\"+oe.join(\"|\")+\")\\\\b\"},{begin:\"\\\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\\\b\"},{begin:/:/,end:/[;}{]/,contains:[n.BLOCK_COMMENT,r,n.HEXCOLOR,n.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.IMPORTANT]},{begin:\"@(page|font-face)\",keywords:{$pattern:i,keyword:\"@page @font-face\"}},{begin:\"@\",end:\"[{;]\",returnBegin:!0,keywords:{$pattern:/[a-z-]+/,keyword:\"and or not only\",attribute:ie.join(\" \")},contains:[{begin:i,className:\"keyword\"},{begin:/[a-z-]+(?=:)/,className:\"attribute\"},r,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.HEXCOLOR,n.CSS_NUMBER_MODE]},n.FUNCTION_DISPATCH]}},grmr_shell:e=>({name:\"Shell Session\",aliases:[\"console\",\"shellsession\"],contains:[{className:\"meta.prompt\",begin:/^\\s{0,3}[/~\\w\\d[\\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\\\](?=\\s*$)/,subLanguage:\"bash\"}}]}),grmr_sql:e=>{const n=e.regex,t=e.COMMENT(\"--\",\"$\"),a=[\"true\",\"false\",\"unknown\"],i=[\"bigint\",\"binary\",\"blob\",\"boolean\",\"char\",\"character\",\"clob\",\"date\",\"dec\",\"decfloat\",\"decimal\",\"float\",\"int\",\"integer\",\"interval\",\"nchar\",\"nclob\",\"national\",\"numeric\",\"real\",\"row\",\"smallint\",\"time\",\"timestamp\",\"varchar\",\"varying\",\"varbinary\"],r=[\"abs\",\"acos\",\"array_agg\",\"asin\",\"atan\",\"avg\",\"cast\",\"ceil\",\"ceiling\",\"coalesce\",\"corr\",\"cos\",\"cosh\",\"count\",\"covar_pop\",\"covar_samp\",\"cume_dist\",\"dense_rank\",\"deref\",\"element\",\"exp\",\"extract\",\"first_value\",\"floor\",\"json_array\",\"json_arrayagg\",\"json_exists\",\"json_object\",\"json_objectagg\",\"json_query\",\"json_table\",\"json_table_primitive\",\"json_value\",\"lag\",\"last_value\",\"lead\",\"listagg\",\"ln\",\"log\",\"log10\",\"lower\",\"max\",\"min\",\"mod\",\"nth_value\",\"ntile\",\"nullif\",\"percent_rank\",\"percentile_cont\",\"percentile_disc\",\"position\",\"position_regex\",\"power\",\"rank\",\"regr_avgx\",\"regr_avgy\",\"regr_count\",\"regr_intercept\",\"regr_r2\",\"regr_slope\",\"regr_sxx\",\"regr_sxy\",\"regr_syy\",\"row_number\",\"sin\",\"sinh\",\"sqrt\",\"stddev_pop\",\"stddev_samp\",\"substring\",\"substring_regex\",\"sum\",\"tan\",\"tanh\",\"translate\",\"translate_regex\",\"treat\",\"trim\",\"trim_array\",\"unnest\",\"upper\",\"value_of\",\"var_pop\",\"var_samp\",\"width_bucket\"],s=[\"create table\",\"insert into\",\"primary key\",\"foreign key\",\"not null\",\"alter table\",\"add constraint\",\"grouping sets\",\"on overflow\",\"character set\",\"respect nulls\",\"ignore nulls\",\"nulls first\",\"nulls last\",\"depth first\",\"breadth first\"],o=r,l=[\"abs\",\"acos\",\"all\",\"allocate\",\"alter\",\"and\",\"any\",\"are\",\"array\",\"array_agg\",\"array_max_cardinality\",\"as\",\"asensitive\",\"asin\",\"asymmetric\",\"at\",\"atan\",\"atomic\",\"authorization\",\"avg\",\"begin\",\"begin_frame\",\"begin_partition\",\"between\",\"bigint\",\"binary\",\"blob\",\"boolean\",\"both\",\"by\",\"call\",\"called\",\"cardinality\",\"cascaded\",\"case\",\"cast\",\"ceil\",\"ceiling\",\"char\",\"char_length\",\"character\",\"character_length\",\"check\",\"classifier\",\"clob\",\"close\",\"coalesce\",\"collate\",\"collect\",\"column\",\"commit\",\"condition\",\"connect\",\"constraint\",\"contains\",\"convert\",\"copy\",\"corr\",\"corresponding\",\"cos\",\"cosh\",\"count\",\"covar_pop\",\"covar_samp\",\"create\",\"cross\",\"cube\",\"cume_dist\",\"current\",\"current_catalog\",\"current_date\",\"current_default_transform_group\",\"current_path\",\"current_role\",\"current_row\",\"current_schema\",\"current_time\",\"current_timestamp\",\"current_path\",\"current_role\",\"current_transform_group_for_type\",\"current_user\",\"cursor\",\"cycle\",\"date\",\"day\",\"deallocate\",\"dec\",\"decimal\",\"decfloat\",\"declare\",\"default\",\"define\",\"delete\",\"dense_rank\",\"deref\",\"describe\",\"deterministic\",\"disconnect\",\"distinct\",\"double\",\"drop\",\"dynamic\",\"each\",\"element\",\"else\",\"empty\",\"end\",\"end_frame\",\"end_partition\",\"end-exec\",\"equals\",\"escape\",\"every\",\"except\",\"exec\",\"execute\",\"exists\",\"exp\",\"external\",\"extract\",\"false\",\"fetch\",\"filter\",\"first_value\",\"float\",\"floor\",\"for\",\"foreign\",\"frame_row\",\"free\",\"from\",\"full\",\"function\",\"fusion\",\"get\",\"global\",\"grant\",\"group\",\"grouping\",\"groups\",\"having\",\"hold\",\"hour\",\"identity\",\"in\",\"indicator\",\"initial\",\"inner\",\"inout\",\"insensitive\",\"insert\",\"int\",\"integer\",\"intersect\",\"intersection\",\"interval\",\"into\",\"is\",\"join\",\"json_array\",\"json_arrayagg\",\"json_exists\",\"json_object\",\"json_objectagg\",\"json_query\",\"json_table\",\"json_table_primitive\",\"json_value\",\"lag\",\"language\",\"large\",\"last_value\",\"lateral\",\"lead\",\"leading\",\"left\",\"like\",\"like_regex\",\"listagg\",\"ln\",\"local\",\"localtime\",\"localtimestamp\",\"log\",\"log10\",\"lower\",\"match\",\"match_number\",\"match_recognize\",\"matches\",\"max\",\"member\",\"merge\",\"method\",\"min\",\"minute\",\"mod\",\"modifies\",\"module\",\"month\",\"multiset\",\"national\",\"natural\",\"nchar\",\"nclob\",\"new\",\"no\",\"none\",\"normalize\",\"not\",\"nth_value\",\"ntile\",\"null\",\"nullif\",\"numeric\",\"octet_length\",\"occurrences_regex\",\"of\",\"offset\",\"old\",\"omit\",\"on\",\"one\",\"only\",\"open\",\"or\",\"order\",\"out\",\"outer\",\"over\",\"overlaps\",\"overlay\",\"parameter\",\"partition\",\"pattern\",\"per\",\"percent\",\"percent_rank\",\"percentile_cont\",\"percentile_disc\",\"period\",\"portion\",\"position\",\"position_regex\",\"power\",\"precedes\",\"precision\",\"prepare\",\"primary\",\"procedure\",\"ptf\",\"range\",\"rank\",\"reads\",\"real\",\"recursive\",\"ref\",\"references\",\"referencing\",\"regr_avgx\",\"regr_avgy\",\"regr_count\",\"regr_intercept\",\"regr_r2\",\"regr_slope\",\"regr_sxx\",\"regr_sxy\",\"regr_syy\",\"release\",\"result\",\"return\",\"returns\",\"revoke\",\"right\",\"rollback\",\"rollup\",\"row\",\"row_number\",\"rows\",\"running\",\"savepoint\",\"scope\",\"scroll\",\"search\",\"second\",\"seek\",\"select\",\"sensitive\",\"session_user\",\"set\",\"show\",\"similar\",\"sin\",\"sinh\",\"skip\",\"smallint\",\"some\",\"specific\",\"specifictype\",\"sql\",\"sqlexception\",\"sqlstate\",\"sqlwarning\",\"sqrt\",\"start\",\"static\",\"stddev_pop\",\"stddev_samp\",\"submultiset\",\"subset\",\"substring\",\"substring_regex\",\"succeeds\",\"sum\",\"symmetric\",\"system\",\"system_time\",\"system_user\",\"table\",\"tablesample\",\"tan\",\"tanh\",\"then\",\"time\",\"timestamp\",\"timezone_hour\",\"timezone_minute\",\"to\",\"trailing\",\"translate\",\"translate_regex\",\"translation\",\"treat\",\"trigger\",\"trim\",\"trim_array\",\"true\",\"truncate\",\"uescape\",\"union\",\"unique\",\"unknown\",\"unnest\",\"update\",\"upper\",\"user\",\"using\",\"value\",\"values\",\"value_of\",\"var_pop\",\"var_samp\",\"varbinary\",\"varchar\",\"varying\",\"versioning\",\"when\",\"whenever\",\"where\",\"width_bucket\",\"window\",\"with\",\"within\",\"without\",\"year\",\"add\",\"asc\",\"collation\",\"desc\",\"final\",\"first\",\"last\",\"view\"].filter(e=>!r.includes(e)),c={begin:n.concat(/\\b/,n.either(...o),/\\s*\\(/),relevance:0,keywords:{built_in:o}};return{name:\"SQL\",case_insensitive:!0,illegal:/[{}]|<\\//,keywords:{$pattern:/\\b[\\w\\.]+/,keyword:((e,{exceptions:n,when:t}={})=>{const a=t;return n=n||[],e.map(e=>e.match(/\\|\\d+$/)||n.includes(e)?e:a(e)?e+\"|0\":e)})(l,{when:e=>e.length<3}),literal:a,type:i,built_in:[\"current_catalog\",\"current_date\",\"current_default_transform_group\",\"current_path\",\"current_role\",\"current_schema\",\"current_transform_group_for_type\",\"current_user\",\"session_user\",\"system_time\",\"system_user\",\"current_time\",\"localtime\",\"current_timestamp\",\"localtimestamp\"]},contains:[{begin:n.either(...s),relevance:0,keywords:{$pattern:/[\\w\\.]+/,keyword:l.concat(s),literal:a,type:i}},{className:\"type\",begin:n.either(\"double precision\",\"large object\",\"with timezone\",\"without timezone\")},c,{className:\"variable\",begin:/@[a-z0-9]+/},{className:\"string\",variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/\"/,end:/\"/,contains:[{begin:/\"\"/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{className:\"operator\",begin:/[-+*/=%^~]|&&?|\\|\\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0}]}},grmr_swift:e=>{const n={match:/\\s+/,relevance:0},t=e.COMMENT(\"/\\\\*\",\"\\\\*/\",{contains:[\"self\"]}),a=[e.C_LINE_COMMENT_MODE,t],i={match:[/\\./,p(...ve,...ke)],className:{2:\"keyword\"}},r={match:m(/\\./,p(...xe)),relevance:0},s=xe.filter(e=>\"string\"==typeof e).concat([\"_|0\"]),o={variants:[{className:\"keyword\",match:p(...xe.filter(e=>\"string\"!=typeof e).concat(Oe).map(Ne),...ke)}]},l={$pattern:p(/\\b\\w+/,/#\\w+/),keyword:s.concat(Ae),literal:Me},c=[i,r,o],d=[{match:m(/\\./,p(...Ce)),relevance:0},{className:\"built_in\",match:m(/\\b/,p(...Ce),/(?=\\()/)}],u={match:/->/,relevance:0},b=[u,{className:\"operator\",relevance:0,variants:[{match:De},{match:`\\\\.(\\\\.|${Re})+`}]}],_=\"([0-9a-fA-F]_*)+\",h={className:\"number\",relevance:0,variants:[{match:\"\\\\b(([0-9]_*)+)(\\\\.(([0-9]_*)+))?([eE][+-]?(([0-9]_*)+))?\\\\b\"},{match:`\\\\b0x(${_})(\\\\.(${_}))?([pP][+-]?(([0-9]_*)+))?\\\\b`},{match:/\\b0o([0-7]_*)+\\b/},{match:/\\b0b([01]_*)+\\b/}]},f=(e=\"\")=>({className:\"subst\",variants:[{match:m(/\\\\/,e,/[0\\\\tnr\"']/)},{match:m(/\\\\/,e,/u\\{[0-9a-fA-F]{1,8}\\}/)}]}),E=(e=\"\")=>({className:\"subst\",match:m(/\\\\/,e,/[\\t ]*(?:[\\r\\n]|\\r\\n)/)}),y=(e=\"\")=>({className:\"subst\",label:\"interpol\",begin:m(/\\\\/,e,/\\(/),end:/\\)/}),w=(e=\"\")=>({begin:m(e,/\"\"\"/),end:m(/\"\"\"/,e),contains:[f(e),E(e),y(e)]}),N=(e=\"\")=>({begin:m(e,/\"/),end:m(/\"/,e),contains:[f(e),y(e)]}),v={className:\"string\",variants:[w(),w(\"#\"),w(\"##\"),w(\"###\"),N(),N(\"#\"),N(\"##\"),N(\"###\")]},k={match:m(/`/,Be,/`/)},O=[k,{className:\"variable\",match:/\\$\\d+/},{className:\"variable\",match:`\\\\$${Le}+`}],x=[{match:/(@|#(un)?)available/,className:\"keyword\",starts:{contains:[{begin:/\\(/,end:/\\)/,keywords:Fe,contains:[...b,h,v]}]}},{className:\"keyword\",match:m(/@/,p(...ze))},{className:\"meta\",match:m(/@/,Be)}],M={match:g(/\\b[A-Z]/),relevance:0,contains:[{className:\"type\",match:m(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,Le,\"+\")},{className:\"type\",match:$e,relevance:0},{match:/[?!]+/,relevance:0},{match:/\\.\\.\\./,relevance:0},{match:m(/\\s+&\\s+/,g($e)),relevance:0}]},S={begin:/</,end:/>/,keywords:l,contains:[...a,...c,...x,u,M]};M.contains.push(S);const A={begin:/\\(/,end:/\\)/,relevance:0,keywords:l,contains:[\"self\",{match:m(Be,/\\s*:/),keywords:\"_|0\",relevance:0},...a,...c,...d,...b,h,v,...O,...x,M]},C={begin:/</,end:/>/,contains:[...a,M]},T={begin:/\\(/,end:/\\)/,keywords:l,contains:[{begin:p(g(m(Be,/\\s*:/)),g(m(Be,/\\s+/,Be,/\\s*:/))),end:/:/,relevance:0,contains:[{className:\"keyword\",match:/\\b_\\b/},{className:\"params\",match:Be}]},...a,...c,...b,h,v,...x,M,A],endsParent:!0,illegal:/[\"']/},R={match:[/func/,/\\s+/,p(k.match,Be,De)],className:{1:\"keyword\",3:\"title.function\"},contains:[C,T,n],illegal:[/\\[/,/%/]},D={match:[/\\b(?:subscript|init[?!]?)/,/\\s*(?=[<(])/],className:{1:\"keyword\"},contains:[C,T,n],illegal:/\\[|%/},I={match:[/operator/,/\\s+/,De],className:{1:\"keyword\",3:\"title\"}},L={begin:[/precedencegroup/,/\\s+/,$e],className:{1:\"keyword\",3:\"title\"},contains:[M],keywords:[...Se,...Me],end:/}/};for(const e of v.variants){const n=e.contains.find(e=>\"interpol\"===e.label);n.keywords=l;const t=[...c,...d,...b,h,v,...O];n.contains=[...t,{begin:/\\(/,end:/\\)/,contains:[\"self\",...t]}]}return{name:\"Swift\",keywords:l,contains:[...a,R,D,{beginKeywords:\"struct protocol class extension enum actor\",end:\"\\\\{\",excludeEnd:!0,keywords:l,contains:[e.inherit(e.TITLE_MODE,{className:\"title.class\",begin:/[A-Za-z$_][\\u00C0-\\u02B80-9A-Za-z$_]*/}),...c]},I,L,{beginKeywords:\"import\",end:/$/,contains:[...a],relevance:0},...c,...d,...b,h,v,...O,...x,M,A]}},grmr_typescript:e=>{const n=we(e),t=[\"any\",\"void\",\"number\",\"boolean\",\"string\",\"object\",\"never\",\"symbol\",\"bigint\",\"unknown\"],a={beginKeywords:\"namespace\",end:/\\{/,excludeEnd:!0,contains:[n.exports.CLASS_REFERENCE]},i={beginKeywords:\"interface\",end:/\\{/,excludeEnd:!0,keywords:{keyword:\"interface extends\",built_in:t},contains:[n.exports.CLASS_REFERENCE]},r={$pattern:be,keyword:me.concat([\"type\",\"namespace\",\"interface\",\"public\",\"private\",\"protected\",\"implements\",\"declare\",\"abstract\",\"readonly\",\"enum\",\"override\"]),literal:pe,built_in:ye.concat(t),\"variable.language\":Ee},s={className:\"meta\",begin:\"@[A-Za-z$_][0-9A-Za-z$_]*\"},o=(e,n,t)=>{const a=e.contains.findIndex(e=>e.label===n);if(-1===a)throw Error(\"can not find mode to replace\");e.contains.splice(a,1,t)};return Object.assign(n.keywords,r),n.exports.PARAMS_CONTAINS.push(s),n.contains=n.contains.concat([s,a,i]),o(n,\"shebang\",e.SHEBANG()),o(n,\"use_strict\",{className:\"meta\",relevance:10,begin:/^\\s*['\"]use strict['\"]/}),n.contains.find(e=>\"func.def\"===e.label).relevance=0,Object.assign(n,{name:\"TypeScript\",aliases:[\"ts\",\"tsx\"]}),n},grmr_vbnet:e=>{const n=e.regex,t=/\\d{1,2}\\/\\d{1,2}\\/\\d{4}/,a=/\\d{4}-\\d{1,2}-\\d{1,2}/,i=/(\\d|1[012])(:\\d+){0,2} *(AM|PM)/,r=/\\d{1,2}(:\\d{1,2}){1,2}/,s={className:\"literal\",variants:[{begin:n.concat(/# */,n.either(a,t),/ *#/)},{begin:n.concat(/# */,r,/ *#/)},{begin:n.concat(/# */,i,/ *#/)},{begin:n.concat(/# */,n.either(a,t),/ +/,n.either(i,r),/ *#/)}]},o=e.COMMENT(/'''/,/$/,{contains:[{className:\"doctag\",begin:/<\\/?/,end:/>/}]}),l=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\\t ]|^)REM(?=\\s)/}]});return{name:\"Visual Basic .NET\",aliases:[\"vb\"],case_insensitive:!0,classNameAliases:{label:\"symbol\"},keywords:{keyword:\"addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield\",built_in:\"addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort\",type:\"boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort\",literal:\"true false nothing\"},illegal:\"//|\\\\{|\\\\}|endif|gosub|variant|wend|^\\\\$ \",contains:[{className:\"string\",begin:/\"(\"\"|[^/n])\"C\\b/},{className:\"string\",begin:/\"/,end:/\"/,illegal:/\\n/,contains:[{begin:/\"\"/}]},s,{className:\"number\",relevance:0,variants:[{begin:/\\b\\d[\\d_]*((\\.[\\d_]+(E[+-]?[\\d_]+)?)|(E[+-]?[\\d_]+))[RFD@!#]?/},{begin:/\\b\\d[\\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\\dA-F_]+((U?[SIL])|[%&])?/},{begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},{className:\"label\",begin:/^\\w+:/},o,l,{className:\"meta\",begin:/[\\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\\b/,end:/$/,keywords:{keyword:\"const disable else elseif enable end externalsource if region then\"},contains:[l]}]}},grmr_yaml:e=>{const n=\"true false yes no null\",t=\"[\\\\w#;/?:@&=+$,.~*'()[\\\\]]+\",a={className:\"string\",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/\"/,end:/\"/},{begin:/\\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:\"template-variable\",variants:[{begin:/\\{\\{/,end:/\\}\\}/},{begin:/%\\{/,end:/\\}/}]}]},i=e.inherit(a,{variants:[{begin:/'/,end:/'/},{begin:/\"/,end:/\"/},{begin:/[^\\s,{}[\\]]+/}]}),r={end:\",\",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},s={begin:/\\{/,end:/\\}/,contains:[r],illegal:\"\\\\n\",relevance:0},o={begin:\"\\\\[\",end:\"\\\\]\",contains:[r],illegal:\"\\\\n\",relevance:0},l=[{className:\"attr\",variants:[{begin:\"\\\\w[\\\\w :\\\\/.-]*:(?=[ \\t]|$)\"},{begin:'\"\\\\w[\\\\w :\\\\/.-]*\":(?=[ \\t]|$)'},{begin:\"'\\\\w[\\\\w :\\\\/.-]*':(?=[ \\t]|$)\"}]},{className:\"meta\",begin:\"^---\\\\s*$\",relevance:10},{className:\"string\",begin:\"[\\\\|>]([1-9]?[+-])?[ ]*\\\\n( +)[^ ][^\\\\n]*\\\\n(\\\\2[^\\\\n]+\\\\n?)*\"},{begin:\"<%[%=-]?\",end:\"[%-]?%>\",subLanguage:\"ruby\",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:\"type\",begin:\"!\\\\w+!\"+t},{className:\"type\",begin:\"!<\"+t+\">\"},{className:\"type\",begin:\"!\"+t},{className:\"type\",begin:\"!!\"+t},{className:\"meta\",begin:\"&\"+e.UNDERSCORE_IDENT_RE+\"$\"},{className:\"meta\",begin:\"\\\\*\"+e.UNDERSCORE_IDENT_RE+\"$\"},{className:\"bullet\",begin:\"-(?=[ ]|$)\",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:\"number\",begin:\"\\\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\\\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\\\.[0-9]*)?([ \\\\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\\\b\"},{className:\"number\",begin:e.C_NUMBER_RE+\"\\\\b\",relevance:0},s,o,a],c=[...l];return c.pop(),c.push(i),r.contains=c,{name:\"YAML\",case_insensitive:!0,aliases:[\"yml\"],contains:l}}});const je=ne;for(const e of Object.keys(Ue)){const n=e.replace(\"grmr_\",\"\").replace(\"_\",\"-\");je.registerLanguage(n,Ue[e])}return je}();\"object\"==typeof exports&&\"undefined\"!=typeof module&&(module.exports=hljs);\n"
  },
  {
    "path": "web/playground/.eslintrc.cjs",
    "content": "module.exports = {\n  root: true,\n  env: { browser: true, es2020: true },\n  extends: [\n    \"eslint:recommended\",\n    \"plugin:react/recommended\",\n    \"plugin:react/jsx-runtime\",\n    \"plugin:react-hooks/recommended\",\n  ],\n  ignorePatterns: [\"dist\", \".eslintrc.cjs\"],\n  parserOptions: { ecmaVersion: \"latest\", sourceType: \"module\" },\n  settings: { react: { version: \"18.2\" } },\n  plugins: [\"react-refresh\"],\n  rules: {\n    \"react-refresh/only-export-components\": [\n      \"warn\",\n      { allowConstantExport: true },\n    ],\n  },\n};\n"
  },
  {
    "path": "web/playground/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# data\n/public/data\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\nbook.json\n"
  },
  {
    "path": "web/playground/README.md",
    "content": "# PRQL Playground\n\nA fast-feedback compiler from PRQL to SQL, hosted at\n<https://prql-lang.org/playground/>\n\nTo run locally,\n[set up a development environment](https://prql-lang.org/book/project/contributing/development.html),\nand then run:\n\n```sh\ntask web:run-playground\n```\n\n...or use the commands which that command calls from\n[our Taskfile](../Taskfile.yaml).\n"
  },
  {
    "path": "web/playground/generateBook.cjs",
    "content": "const { readdir, stat, readFile, writeFile } = require(\"fs/promises\");\nconst { join, relative, sep, normalize, basename } = require(\"path\");\nconst { EOL } = require(\"os\");\n\n/**\n * Get all markdown files in given dir\n * @param {string} dirPath\n */\nasync function* getAllFiles(dirPath) {\n  const files = await readdir(dirPath);\n  files.sort((a, b) => +isFile(a) - +isFile(b));\n\n  for (const file of files) {\n    const fullPath = join(dirPath, file);\n    if ((await stat(fullPath)).isDirectory()) {\n      yield fullPath;\n      yield* getAllFiles(fullPath);\n    } else {\n      if (fullPath.endsWith(\".md\")) {\n        yield fullPath;\n      }\n    }\n  }\n}\n\nfunction depth(path) {\n  return path.split(sep).length;\n}\n\nfunction isFile(path) {\n  return path.endsWith(\".md\");\n}\n\n/**\n * Get all prql code snippets from a markdown file\n * @param {string} content\n * @param {string} file\n * @returns {{title:string, prql:string}[]}\n */\nfunction getSnippets(content, file) {\n  const name = file.trim().toLowerCase().replace(/\\s/g, \"_\");\n  let heading = \"\";\n  let prql = null;\n  const arr = [];\n  let index = 1;\n  const titles = new Set();\n  content.split(/\\r\\n|\\n/).forEach((line) => {\n    if (prql == null && line.startsWith(\"#\") && line.includes(\"# \")) {\n      const spaceIndex = line.indexOf(\"# \");\n      heading = line\n        .slice(spaceIndex + 2)\n        .trim()\n        .toLowerCase()\n        .replace(/\\s/g, \"_\");\n      return;\n    }\n    if (line.trim() === \"```prql\") {\n      prql = \"\";\n      return;\n    }\n    if (prql != null && line.trim() === \"```\") {\n      let title = heading || name;\n      if (titles.has(title)) {\n        title += `_${++index}`;\n      } else {\n        index = 1;\n      }\n      arr.push({\n        title: title + \".prql\",\n        prql: prql.trim(),\n      });\n      titles.add(title);\n      prql = null;\n      return;\n    }\n    if (prql != null) {\n      prql = prql + line + EOL;\n    }\n  });\n  if (arr.length !== titles.size) {\n    throw new Error(\"duplicate titles\");\n  }\n  return arr;\n}\n\n(async () => {\n  const fileObject = {};\n  const dir = join(__dirname, \"..\", \"book\", \"src\");\n  const files = [];\n  let minDepth = 1e10;\n  for await (const file of getAllFiles(dir)) {\n    files.push(file);\n    minDepth = Math.min(depth(file));\n  }\n\n  for (const filePath of files) {\n    const relativeFile = relative(dir, filePath);\n    const snippets = isFile(filePath)\n      ? getSnippets(\n          (await readFile(filePath)).toString(),\n          basename(filePath).replace(/\\..+/g, \"\").trim(),\n        )\n      : [];\n    if (!snippets.length && isFile(filePath)) {\n      continue;\n    }\n    const dept = depth(filePath) - minDepth;\n    fileObject[relativeFile] = [\n      \"\", // editor\n      \"\", // content\n      dept, // depth\n      normalize(join(relativeFile, \"..\")), // parent\n      relativeFile, // id\n      basename(relativeFile).replace(/\\..+/g, \"\").trim(), // name\n    ];\n    for (const snippet of snippets) {\n      const id = relativeFile + \"_\" + snippet.title;\n      fileObject[id] = [\n        \"sql\",\n        snippet.prql,\n        dept + 1,\n        relativeFile,\n        id,\n        snippet.title,\n      ];\n    }\n  }\n\n  // remove empty folders\n  const keys = Object.keys(fileObject);\n  const parents = new Set();\n  Object.values(fileObject).forEach((v) => parents.add(v[3]));\n  for (const key of keys) {\n    if (!parents.has(key) && !fileObject[key][0]) {\n      delete fileObject[key];\n    }\n  }\n\n  await writeFile(join(\"src\", \"book.json\"), JSON.stringify(fileObject));\n})().catch((e) => {\n  console.error(e);\n  process.exit(1);\n});\n"
  },
  {
    "path": "web/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#222222\" />\n    <meta name=\"description\" content=\"PRQL Playground\" />\n    <link rel=\"apple-touch-icon\" href=\"/icon192.png\" />\n    <!--\n      manifest.json provides metadata used when your web app is installed on a\n      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/\n    -->\n    <link rel=\"manifest\" href=\"/manifest.json\" />\n    <title>PRQL Playground</title>\n  </head>\n\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n    <script type=\"module\" src=\"/src/main.jsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "web/playground/package.json",
    "content": "{\n  \"browserslist\": {\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ],\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ]\n  },\n  \"dependencies\": {\n    \"@duckdb/duckdb-wasm\": \"^1.32.0\",\n    \"@monaco-editor/react\": \"^4.7.0\",\n    \"@testing-library/jest-dom\": \"^6.9.0\",\n    \"@testing-library/react\": \"^16.3.0\",\n    \"@testing-library/user-event\": \"^14.6.0\",\n    \"monaco-editor\": \"^0.55.0\",\n    \"prqlc\": \"file:../../prqlc/bindings/js\",\n    \"react\": \"^19.2.0\",\n    \"react-dom\": \"^19.2.0\",\n    \"react-syntax-highlighter\": \"^16.1.0\",\n    \"web-vitals\": \"^5.1.0\",\n    \"yaml\": \"^2.8.0\"\n  },\n  \"devDependencies\": {\n    \"@vitejs/plugin-react\": \"^6.0.1\",\n    \"vite\": \"^8.0.1\",\n    \"vite-plugin-wasm\": \"^3.6.0\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"react-app\",\n      \"react-app/jest\"\n    ]\n  },\n  \"homepage\": \"https://prql-lang.org/playground/playground/\",\n  \"name\": \"prql-playground\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite --host 0.0.0.0\",\n    \"build\": \"vite build\",\n    \"prepare\": \"rsync -ai --checksum --delete ../../prqlc/prqlc/tests/integration/data/ public/data/ && node generateBook.cjs\",\n    \"preview\": \"vite preview\"\n  },\n  \"version\": \"0.13.12\"\n}\n"
  },
  {
    "path": "web/playground/public/manifest.json",
    "content": "{\n  \"background_color\": \"#ffffff\",\n  \"display\": \"standalone\",\n  \"icons\": [\n    {\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"src\": \"favicon.ico\",\n      \"type\": \"image/x-icon\"\n    },\n    {\n      \"sizes\": \"192x192\",\n      \"src\": \"icon192.png\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"name\": \"PRQL Playground\",\n  \"short_name\": \"PRQL Playground\",\n  \"start_url\": \".\",\n  \"theme_color\": \"#222222\"\n}\n"
  },
  {
    "path": "web/playground/public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "web/playground/src/app/App.css",
    "content": ".main {\n  height: 100%;\n  display: flex;\n}\n.main > * {\n  flex-grow: 1;\n}\n.sidebar {\n  flex: 0 0 200px;\n}\n"
  },
  {
    "path": "web/playground/src/app/App.jsx",
    "content": "import \"./App.css\";\nimport Workbench from \"../workbench/Workbench\";\nimport Sidebar from \"../sidebar/Sidebar\";\nimport examples from \"../examples\";\nimport book from \"../book.json\";\nimport * as duckdb from \"../workbench/duckdb\";\n\nimport React from \"react\";\n\nfunction loadLocalStorage() {\n  return JSON.parse(localStorage.getItem(\"files\")) || {};\n}\n\nfunction saveLocalStorage(files) {\n  return localStorage.setItem(\"files\", JSON.stringify(files));\n}\n\nconst chinook = duckdb.CHINOOK_TABLES.reduce((lib, table) => {\n  return Object.assign(lib, {\n    [table + \".prql\"]: [\"arrow\", `from ${table}\\ntake 10`],\n  });\n}, {});\n\nclass App extends React.Component {\n  workbenchActions = null;\n  state = {\n    library: {\n      examples,\n      tables: chinook,\n      book,\n      \"local storage\": loadLocalStorage(),\n    },\n  };\n\n  setWorkbenchActions = (callables) => {\n    this.workbenchActions = callables;\n  };\n\n  componentDidMount() {\n    let defaultFile = \"introduction.prql\";\n    this.workbenchActions.loadFile(defaultFile, examples[defaultFile]);\n  }\n\n  saveFile(filename, content) {\n    const localStorage = {\n      ...this.state.library[\"local storage\"],\n      [filename]: content,\n    };\n    this.setState({\n      library: { ...this.state.library, \"local storage\": localStorage },\n    });\n    saveLocalStorage(localStorage);\n  }\n\n  render() {\n    return (\n      <div className=\"main\">\n        <Sidebar\n          library={this.state.library}\n          onLoadFile={(f, c) => this.workbenchActions.loadFile(f, c)}\n        />\n\n        <Workbench\n          setCallables={this.setWorkbenchActions}\n          onSaveFile={(f, c) => this.saveFile(f, c)}\n        />\n      </div>\n    );\n  }\n}\n\nexport default App;\n"
  },
  {
    "path": "web/playground/src/examples.js",
    "content": "const examples = {\n  \"introduction.prql\": [\n    \"sql\",\n    `from invoices                        # A PRQL query begins with a table\n                                     # Subsequent lines \"transform\" (modify) it\nderive {                             # \"derive\" adds columns to the result\n  transaction_fee = 0.8,             # \"=\" sets a column name\n  income = total - transaction_fee   # Calculations can use other column names\n}\n# starts a comment; commenting out a line leaves a valid query\nfilter income > 5                    # \"filter\" replaces both of SQL's WHERE & HAVING\nfilter invoice_date >= @2010-01-16   # Clear date syntax\ngroup customer_id (                  # \"group\" performs the pipeline in (...) on each group\n  aggregate {                        # \"aggregate\" reduces each group to a single row\n    sum_income = sum income,         # ... using SQL SUM(), COUNT(), etc. functions\n    ct = count customer_id,          #\n  }\n)\njoin c=customers (==customer_id)     # join on \"customer_id\" from both tables\nderive name = f\"{c.last_name}, {c.first_name}\" # F-strings like Python\nderive db_version = s\"version()\"     # S-string offers escape hatch to SQL\nselect {                             # \"select\" passes along only the named columns\n  c.customer_id, name, sum_income, ct, db_version,\n}                                    # trailing commas always ignored\nsort {-sum_income}                   # \"sort\" sorts the result; \"-\" is decreasing order\ntake 1..10                           # Limit to a range - could also be \"take 10\"\n#\n# The \"output.sql\" tab at right shows the SQL generated from this PRQL query\n# The \"output.arrow\" tab shows the result of the query\n`,\n  ],\n\n  \"let-table-0.prql\": [\n    \"sql\",\n    `let soundtracks = (\n  from playlists\n  filter name == 'TV Shows'\n  join pt=playlist_track (==playlist_id)\n  select pt.track_id\n)\n\nlet high_energy = (\n  from genres\n  filter name == 'Rock And Roll' || name == 'Hip Hop/Rap'\n)\n\nfrom t=tracks\n\n# anti-join soundtracks\njoin side:left s=soundtracks (==track_id)\nfilter s.track_id == null\n\n# limit to kicker genres\njoin g=high_energy (==genre_id)\n\n# format output\nselect {t.track_id, track = t.name, genre = g.name}\ntake 10\n`,\n  ],\n\n  \"artists-0.prql\": [\n    \"sql\",\n    `from tracks\nselect {album_id, name, unit_price}\nsort {-unit_price, name}\ngroup album_id (\n    aggregate {\n    track_count = count name,\n    album_price = sum unit_price\n    }\n)\njoin albums (==album_id)\ngroup artist_id (\n    aggregate {\n    track_count = sum track_count,\n    artist_price = sum album_price\n    }\n)\njoin artists (==artist_id)\nselect {artists.name, artist_price, track_count}\nsort {-artist_price}\nderive avg_track_price = artist_price / track_count\n`,\n  ],\n};\nexport default examples;\n"
  },
  {
    "path": "web/playground/src/highlight.css",
    "content": "/* Highlight.js obsidian theme */\npre code.hljs {\n  display: block;\n  overflow-x: auto;\n}\ncode.hljs {\n  padding: 3px 5px;\n}\n.hljs {\n  color: #e0e2e4;\n}\n.hljs-keyword,\n.hljs-literal,\n.hljs-selector-id,\n.hljs-selector-tag {\n  color: #e4a24b;\n}\n.hljs-number {\n  color: #ffcd22;\n}\n.hljs-params {\n  color: #5b8fc5;\n}\n.hljs-link,\n.hljs-regexp {\n  color: #d39745;\n}\n.hljs-meta {\n  color: #557182;\n}\n.hljs-addition,\n.hljs-built_in,\n.hljs-bullet,\n.hljs-emphasis,\n.hljs-module,\n.hljs-name,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-subst,\n.hljs-tag,\n.hljs-template-tag,\n.hljs-template-variable,\n.hljs-type,\n.hljs-variable {\n  color: #8cbbad;\n}\n.hljs-string,\n.hljs-symbol {\n  color: #ec7600;\n}\n.hljs-comment,\n.hljs-deletion,\n.hljs-quote {\n  color: #818e96;\n}\n.hljs-selector-class {\n  color: #a082bd;\n}\n.hljs-doctag,\n.hljs-keyword,\n.hljs-literal,\n.hljs-name,\n.hljs-section,\n.hljs-selector-tag,\n.hljs-strong,\n.hljs-title,\n.hljs-type {\n  font-weight: 700;\n}\n.hljs-class .hljs-title,\n.hljs-code,\n.hljs-section,\n.hljs-title.class_ {\n  color: #fff;\n}\n"
  },
  {
    "path": "web/playground/src/index.css",
    "content": "body {\n  margin: 0;\n  font-family:\n    -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\",\n    \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n\n  background-color: #2f2f2f;\n  color: #fff;\n}\n\n#root {\n  height: 100vh !important;\n}\n\n/* Inter ExtraBold */\n@font-face {\n  font-family: \"InterExtraBold\";\n  font-weight: bold;\n  font-style: normal;\n  font-display: swap;\n  src:\n    local(\"\"),\n    url(\"https://prql-lang.org/fonts/inter-extra-bold.woff2\") format(\"woff2\"),\n    url(\"https://prql-lang.org/fonts/inter-extra-bold.woff\") format(\"woff\");\n}\n\nh1 {\n  font-family:\n    \"InterExtraBold\",\n    -apple-system,\n    BlinkMacSystemFont,\n    \"Segoe UI\",\n    Roboto,\n    Oxygen-Sans,\n    Ubuntu,\n    Cantarell,\n    \"Helvetica Neue\",\n    sans-serif;\n}\n"
  },
  {
    "path": "web/playground/src/main.jsx",
    "content": "import React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport \"./index.css\";\nimport \"./highlight.css\";\nimport App from \"./app/App\";\nimport reportWebVitals from \"./reportWebVitals\";\n\nconst root = ReactDOM.createRoot(document.getElementById(\"root\"));\nroot.render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"
  },
  {
    "path": "web/playground/src/output/Output.css",
    "content": ".tab-content.arrow {\n  padding: 1.5rem;\n  overflow-x: scroll;\n}\n\n.arrow table {\n  border-collapse: collapse;\n}\n\n.arrow table td,\n.arrow table th {\n  min-width: 2rem;\n  padding: 2px 0.5rem;\n  text-align: right;\n}\n\n.arrow table thead {\n  border-bottom: 1px solid #444;\n}\n\n.arrow table tbody tr:not(:first-child) td {\n  border-top: 1px solid #444;\n}\n\n/* .arrow table tbody tr:nth-child(2n) td {\n  background-color: #333;\n} */\n"
  },
  {
    "path": "web/playground/src/output/Output.jsx",
    "content": "import \"./Output.css\";\n\nimport React from \"react\";\n\nimport { Light as SyntaxHighlighter } from \"react-syntax-highlighter\";\nimport sql from \"react-syntax-highlighter/dist/esm/languages/hljs/sql\";\nimport yaml from \"react-syntax-highlighter/dist/esm/languages/hljs/yaml\";\n\nSyntaxHighlighter.registerLanguage(\"sql\", sql);\nSyntaxHighlighter.registerLanguage(\"yaml\", yaml);\n\nfunction Tab(props) {\n  return (\n    <button\n      className={`tab-title ${props.parent.tab === props.name ? \"active\" : \"\"}`}\n      onClick={() => props.parent.onTabChange(props.name)}\n    >\n      {props.text}\n    </button>\n  );\n}\n\nclass Output extends React.Component {\n  state = {\n    justCopied: false,\n  };\n\n  render() {\n    return (\n      <div className=\"tab\">\n        <div className=\"tab-top\">\n          <Tab text=\"Compiled&nbsp;SQL\" name=\"sql\" parent={this.props} />\n          <Tab text=\"Query&nbsp;Results\" name=\"arrow\" parent={this.props} />\n          <Tab text=\"PL.yaml\" name=\"pl\" parent={this.props} />\n          <Tab text=\"RQ.yaml\" name=\"rq\" parent={this.props} />\n\n          <div className=\"spacer\"></div>\n\n          <button className=\"action\" onClick={() => this.copyOutput()}>\n            {this.state.justCopied ? \"Copied!\" : \"Copy to clipboard\"}\n          </button>\n        </div>\n\n        {this.renderContent()}\n      </div>\n    );\n  }\n\n  renderContent() {\n    if (!this.props.content) {\n      return <div className=\"tab-content\"></div>;\n    }\n    if (this.props.tab === \"sql\") {\n      return (\n        <SyntaxHighlighter language=\"sql\" useInlineStyles={false}>\n          {this.props.content.sql}\n        </SyntaxHighlighter>\n      );\n    }\n    if (this.props.tab === \"arrow\" && this.props.content.arrow) {\n      const arrow = this.props.content.arrow;\n\n      const header = arrow.schema.fields.map((f, index) => {\n        return <th key={index}>{f.name}</th>;\n      });\n\n      const converters = arrow.schema.fields.map((f) => {\n        const typo = f.type.toString();\n        if (typo.startsWith(\"Timestamp\")) {\n          // TODO: handle timezone (which Date does not support)\n\n          // HACK: due to bug in arrow or duckdb, we are getting MICROSECOND here,\n          // but the values are actually milliseconds. I'm not sure what is going on,\n          // so let's just assume the values will always be in milliseconds.\n          /*\n          if (typo.endsWith(\"<SECOND>\")) {\n            return (x) => new Date(x * 1000).toISOString();\n          }\n          if (typo.endsWith(\"<MILLISECOND>\")) {\n            return (x) => new Date(x).toISOString();\n          }\n          if (typo.endsWith(\"<MICROSECOND>\")) {\n            return (x) => new Date(x / 1000).toISOString();\n          }\n          if (typo.endsWith(\"<NANOSECOND>\")) {\n            return (x) => new Date(x / 1000000).toISOString();\n          }\n          */\n          return (x) => new Date(x).toISOString();\n        }\n        return (x) => x;\n      });\n\n      const data = arrow.toArray().map((x) => [...x]);\n      const rows = data.map((x, index) => {\n        const cells = x.map(([_name, value], index) => (\n          <td key={index}>{\"\" + converters[index](value)}</td>\n        ));\n\n        return <tr key={index}>{cells}</tr>;\n      });\n\n      // console.log(arrow, arrow.schema.fields, arrow.toArray());\n\n      return (\n        <div className=\"tab-content arrow\">\n          <table className=\"tab-content\">\n            <thead>\n              <tr>{header}</tr>\n            </thead>\n            <tbody>{rows}</tbody>\n          </table>\n        </div>\n      );\n    }\n    if (this.props.tab === \"pl\" && this.props.content.pl) {\n      return (\n        <SyntaxHighlighter language=\"yaml\" useInlineStyles={false}>\n          {this.props.content.pl}\n        </SyntaxHighlighter>\n      );\n    }\n    if (this.props.tab === \"rq\" && this.props.content.rq) {\n      return (\n        <SyntaxHighlighter language=\"yaml\" useInlineStyles={false}>\n          {this.props.content.rq}\n        </SyntaxHighlighter>\n      );\n    }\n    return <div className=\"tab-content\"></div>;\n  }\n\n  async copyOutput() {\n    try {\n      await navigator.clipboard.writeText(this.props.content[this.props.tab]);\n\n      this.setState({ justCopied: true });\n\n      await new Promise((r) => window.setTimeout(r, 2000));\n      this.setState({ justCopied: false });\n    } catch (e) {\n      console.error(e);\n    }\n  }\n}\n\nexport default Output;\n"
  },
  {
    "path": "web/playground/src/reportWebVitals.js",
    "content": "const reportWebVitals = (onPerfEntry) => {\n  if (onPerfEntry && onPerfEntry instanceof Function) {\n    import(\"web-vitals\").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n      getCLS(onPerfEntry);\n      getFID(onPerfEntry);\n      getFCP(onPerfEntry);\n      getLCP(onPerfEntry);\n      getTTFB(onPerfEntry);\n    });\n  }\n};\n\nexport default reportWebVitals;\n"
  },
  {
    "path": "web/playground/src/setupTests.js",
    "content": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).toHaveTextContent(/react/i)\n// learn more: https://github.com/testing-library/jest-dom\nimport \"@testing-library/jest-dom\";\n"
  },
  {
    "path": "web/playground/src/sidebar/Sidebar.css",
    "content": ".sidebar {\n  padding-top: 1rem;\n  min-width: fit-content;\n  overflow-y: auto;\n}\nsection:not(:last-child) {\n  border-bottom: 1px solid #666;\n  padding-bottom: 1rem;\n}\nsection h1 {\n  font-size: 16px;\n  margin: 0;\n}\nsection h2 {\n  text-transform: uppercase;\n  font-weight: bold;\n  font-size: 14px;\n  margin: 0;\n}\n\n.fileRow::before {\n  content: \"\\00a0\"; /* non-breaking space to indent fileRows */\n  margin-right: 5px;\n}\n.fileRow:hover {\n  cursor: pointer;\n  background-color: #ffffff1f;\n}\n\n.folderRow ~ .fileRow:not(.folderRow)::before {\n  content: \"\";\n  margin-right: 5px;\n}\n\n.folderRow {\n  text-transform: capitalize;\n}\n\n.folderRow::before {\n  /* disclosure triangle to show hierarchy */\n  content: \"\\00a0\\00a0\\25b7\"; /* two non-breaking spaces and a white right triangle */\n  font-size: 0.75em; /* a little smaller */\n  margin-right: 5px;\n  display: inline-block;\n  transition: transform 0.3s ease;\n  transform-origin: center;\n}\n\n.folderRow.open::before {\n  transform: rotate(90deg);\n}\n\nsection h1,\nsection h2,\nsection p,\n.fileRow {\n  padding: 2px 1rem 2px 1rem;\n  display: block;\n  line-height: 1;\n}\n\nsection ul {\n  padding-left: 2px;\n}\nsection a {\n  color: lightblue;\n}\nsection a:visited {\n  color: plum;\n}\n"
  },
  {
    "path": "web/playground/src/sidebar/Sidebar.jsx",
    "content": "import \"./Sidebar.css\";\nimport React from \"react\";\nimport { useState } from \"react\";\n\nfunction Sidebar({ library, onLoadFile }) {\n  function loadFile(section, file) {\n    onLoadFile(file, library[section][file]);\n  }\n\n  function toggleFolder(id) {\n    openFolders[id] = !Boolean(openFolders[id]);\n    setOpenFolders(() => ({ ...openFolders }));\n  }\n\n  function handleClick(section, file, id) {\n    if (isFile(file)) {\n      loadFile(section, file);\n    } else {\n      toggleFolder(id);\n    }\n  }\n\n  function isFile(path) {\n    return path.endsWith(\".prql\");\n  }\n\n  const sections = [];\n  const [openFolders, setOpenFolders] = useState({});\n\n  for (const [section, files] of Object.entries(library)) {\n    const fileRows = [];\n    for (const [index, filename] of Object.keys(files).entries()) {\n      const array = files[filename];\n      const depth = array[2];\n      const parent = array[3];\n      const id = array[4];\n      const name = array[5];\n      fileRows.push(\n        <React.Fragment key={index}>\n          {(parent == null || openFolders[parent] || depth === 0) && (\n            <div\n              className={\n                \"fileRow \" +\n                (isFile(filename) ? \" \" : \" folderRow \") +\n                (openFolders[id] ? \" open \" : \" \")\n              }\n              style={{ marginLeft: `${12 * depth}px` }}\n              onClick={() => handleClick(section, filename, id)}\n            >\n              {name ?? filename}\n            </div>\n          )}\n        </React.Fragment>,\n      );\n    }\n\n    sections.push(\n      <section key={section}>\n        <h2>{section}</h2>\n\n        {fileRows}\n      </section>,\n    );\n  }\n\n  return (\n    <div className=\"sidebar\">\n      <section>\n        <h1>PRQL Playground</h1>\n      </section>\n      <section>\n        <h2>External links</h2>\n        <div className=\"fileRow\">\n          <a\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            href=\"https://prql-lang.org\"\n          >\n            PRQL Website &#8599;\n          </a>\n        </div>\n        <div className=\"fileRow\">\n          <a\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            href=\"https://prql-lang.org/book/\"\n          >\n            Book &#8599;\n          </a>\n        </div>\n      </section>\n\n      {sections}\n    </div>\n  );\n}\n\nexport default Sidebar;\n"
  },
  {
    "path": "web/playground/src/workbench/Workbench.css",
    "content": ".column {\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n  position: relative;\n  overflow: hidden;\n}\n.tabs {\n  display: flex;\n  flex-grow: 1;\n  overflow-y: hidden;\n}\n.tab {\n  flex-grow: 1;\n  flex-basis: 0;\n  max-width: 50%;\n  height: 100%;\n\n  display: flex;\n  flex-direction: column;\n  overflow-x: hidden;\n}\n.tab:not(:first-child) {\n  border-left: 1px solid #666;\n}\n.tab > * {\n  flex-grow: 1;\n}\n.tab-top {\n  flex-grow: 0;\n  display: flex;\n  align-items: center;\n}\n.tab-top button {\n  border: 0;\n  background: dimgrey;\n  color: inherit;\n  font-size: inherit;\n  font-family: inherit;\n  cursor: pointer;\n}\n.tab-top .action {\n  padding: 2px 1em;\n  font-size: 80%;\n  text-decoration: underline;\n  color: lightblue;\n}\n.spacer {\n  flex-grow: 1;\n}\n.tab-title {\n  flex-grow: 0;\n  align-self: start;\n  padding: 3px 0.75em;\n  display: inline;\n}\n.tab-title.active {\n  background: #222;\n}\n.error-pane {\n  padding: 1rem;\n  margin: 0;\n  width: 100%;\n\n  max-height: 50%;\n  overflow-y: auto;\n  flex-shrink: 0;\n\n  font-family: monospace;\n  white-space: pre-wrap;\n\n  border-top: 1px solid;\n  border-color: red;\n  background-color: #552222;\n}\n.tab-content {\n  background: #222;\n}\npre.hljs {\n  margin: 0;\n  background: #222;\n  height: 100%;\n  overflow-y: scroll;\n  padding-left: 2em;\n}\npre.hljs code {\n  font-size: 14px;\n}\n"
  },
  {
    "path": "web/playground/src/workbench/Workbench.jsx",
    "content": "import \"./Workbench.css\";\n\nimport * as prql from \"prqlc/dist/bundler\";\nimport React from \"react\";\nimport YAML from \"yaml\";\n\nimport Editor, { loader } from \"@monaco-editor/react\";\nimport * as monaco from \"monaco-editor\";\nimport * as monacoTheme from \"./monaco-theme.json\";\nimport prqlSyntax from \"./prql-syntax\";\n\nimport Output from \"../output/Output\";\nimport * as duckdb from \"./duckdb\";\n\nloader.config({ monaco });\n\nclass Workbench extends React.Component {\n  monaco = null;\n  editor = null;\n\n  duckdb = null;\n\n  state = {\n    filename: \"input.prql\",\n    prql: \"\",\n    output: null,\n    outputTab: \"arrow\",\n\n    prqlError: null,\n    duckdbError: null,\n  };\n\n  loadFile(filename, [outputTab, content]) {\n    this.setState({ filename, outputTab, prql: content });\n    if (this.editor) {\n      this.editor.setValue(content);\n    }\n  }\n\n  componentDidMount() {\n    this.props.setCallables({ loadFile: (f, c) => this.loadFile(f, c) });\n\n    if (!this.duckdb) {\n      this.duckdb = duckdb.init();\n    }\n  }\n\n  beforeEditorMount(monaco) {\n    this.monaco = monaco;\n    monaco.editor.defineTheme(\"blackboard\", monacoTheme);\n    monaco.languages.register({ id: \"prql\", extensions: [\"prql\"] });\n    monaco.languages.setLanguageConfiguration(\"prql\", {\n      comments: {\n        lineComment: \"#\",\n      },\n    });\n    monaco.languages.setMonarchTokensProvider(\"prql\", prqlSyntax);\n  }\n\n  onEditorMount(editor) {\n    this.editor = editor;\n\n    this.compile(editor.getValue());\n  }\n\n  async compile(value) {\n    this.setState({ prql: value });\n\n    let sql;\n    try {\n      sql = prql.compile(value);\n      this.setState({ prqlError: null });\n      this.monaco.editor.setModelMarkers(this.editor.getModel(), \"prql\", []);\n    } catch (e) {\n      if (e instanceof WebAssembly.RuntimeError) {\n        this.setState({\n          prqlError:\n            \"A compiler bug was encountered. Please copy/paste the PRQL query into a new report at:\\n\" +\n            \"   https://github.com/PRQL/prql/issues/new?template=bug_report.yaml\",\n        });\n        return;\n      }\n\n      const errors = JSON.parse(e.message).inner;\n      this.setState({ prqlError: errors[0].display });\n\n      const monacoErrors = errors.map((error) => ({\n        severity: \"error\",\n        message: error.reason,\n        startLineNumber: error.location?.start[0] + 1,\n        startColumn: error.location?.start[1] + 1,\n        endLineNumber: error.location?.end[0] + 1,\n        endColumn: error.location?.end[1] + 1,\n      }));\n      this.monaco.editor.setModelMarkers(\n        this.editor.getModel(),\n        \"prql\",\n        monacoErrors,\n      );\n      return;\n    }\n\n    let pl;\n    try {\n      if (sql) {\n        pl = prql.prql_to_pl(value);\n      }\n    } catch (ignored) {}\n\n    let rq;\n    try {\n      if (pl) {\n        rq = prql.pl_to_rq(pl);\n      }\n    } catch (ignored) {\n      console.log(ignored);\n    }\n\n    let arrow;\n    const c = await (await this.duckdb).connect();\n    try {\n      arrow = await c.query(sql);\n      this.setState({ duckdbError: null });\n    } catch (e) {\n      this.setState({ duckdbError: e.toString() });\n      arrow = null;\n    } finally {\n      c.close();\n    }\n\n    if (pl) {\n      pl = YAML.stringify(JSON.parse(pl));\n    }\n    if (rq) {\n      rq = YAML.stringify(JSON.parse(rq));\n    }\n\n    const output = { sql, arrow, pl, rq };\n\n    this.setState({ output });\n  }\n\n  save() {\n    if (!this.editor) return;\n\n    this.props.onSaveFile(this.state.filename, [\n      this.state.outputTab,\n      this.state.prql,\n    ]);\n  }\n\n  rename() {\n    let filename = prompt(`New name for ${this.state.filename}`);\n    if (filename) {\n      if (!filename.endsWith(\".prql\")) {\n        filename += \".prql\";\n      }\n      this.setState({ filename });\n    }\n  }\n\n  render() {\n    return (\n      <div className=\"column\">\n        <div className=\"tabs\">\n          <div className=\"tab\">\n            <div className=\"tab-top\">\n              <div className=\"tab-title active\">{this.state.filename}</div>\n\n              <div className=\"spacer\"></div>\n\n              <button className=\"action\" onClick={() => this.rename()}>\n                Rename\n              </button>\n              <button className=\"action\" onClick={() => this.save()}>\n                Save\n              </button>\n            </div>\n            <Editor\n              height=\"10rem\"\n              defaultLanguage=\"prql\"\n              defaultValue={this.state.prql}\n              onChange={(v) => this.compile(v)}\n              onMount={(e, m) => this.onEditorMount(e, m)}\n              beforeMount={(m) => this.beforeEditorMount(m)}\n              theme=\"blackboard\"\n              options={{\n                minimap: { enabled: false },\n                scrollBeyondLastLine: false,\n                fontSize: 14,\n              }}\n            />\n          </div>\n\n          <Output\n            content={this.state.output}\n            tab={this.state.outputTab}\n            onTabChange={(tab) => this.setState({ outputTab: tab })}\n          ></Output>\n        </div>\n\n        {/* Display an error message relevant to the tab */}\n        {this.state.prqlError && (\n          <div className=\"error-pane\">{this.state.prqlError}</div>\n        )}\n        {this.state.outputTab === \"arrow\" && this.state.duckdbError && (\n          <div className=\"error-pane\">{this.state.duckdbError}</div>\n        )}\n      </div>\n    );\n  }\n}\n\nexport default Workbench;\n"
  },
  {
    "path": "web/playground/src/workbench/duckdb.js",
    "content": "import * as duckdb from \"@duckdb/duckdb-wasm\";\n\nexport async function init() {\n  const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();\n\n  // Select a bundle based on browser checks\n  const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);\n\n  const worker_url = URL.createObjectURL(\n    new Blob([`importScripts(\"${bundle.mainWorker}\");`], {\n      type: \"text/javascript\",\n    }),\n  );\n\n  // Instantiate the asynchronous version of DuckDB-wasm\n  const worker = new Worker(worker_url);\n  const logger = new duckdb.ConsoleLogger();\n  const db = new duckdb.AsyncDuckDB(logger, worker);\n  await db.instantiate(bundle.mainModule, bundle.pthreadWorker);\n  URL.revokeObjectURL(worker_url);\n\n  await registerChinook(db);\n\n  return db;\n}\n\nexport const CHINOOK_TABLES = [\n  \"albums\",\n  \"artists\",\n  \"customers\",\n  \"employees\",\n  \"genres\",\n  \"invoice_items\",\n  \"invoices\",\n  \"media_types\",\n  \"playlists\",\n  \"playlist_track\",\n  \"tracks\",\n];\n\nasync function registerChinook(db) {\n  const baseUrl = `${window.location.href}/data/chinook`;\n\n  await Promise.all(\n    CHINOOK_TABLES.map(async (table) => {\n      const res = await fetch(`${baseUrl}/${table}.csv`);\n      const text = await res.text();\n\n      db.registerFileText(`${table}.csv`, text);\n    }),\n  );\n\n  const c = await db.connect();\n  for (const table of CHINOOK_TABLES) {\n    await c.insertCSVFromPath(`${table}.csv`, {\n      name: table,\n      detect: true,\n      header: true,\n    });\n  }\n  c.close();\n}\n"
  },
  {
    "path": "web/playground/src/workbench/monaco-theme.json",
    "content": "{\n  \"base\": \"vs-dark\",\n  \"inherit\": true,\n  \"rules\": [\n    {\n      \"background\": \"0C1021\",\n      \"token\": \"\"\n    },\n    {\n      \"foreground\": \"aeaeae\",\n      \"token\": \"comment\"\n    },\n    {\n      \"foreground\": \"d8fa3c\",\n      \"token\": \"constant\"\n    },\n    {\n      \"foreground\": \"ff6400\",\n      \"token\": \"entity\"\n    },\n    {\n      \"foreground\": \"fbde2d\",\n      \"token\": \"keyword\"\n    },\n    {\n      \"foreground\": \"fbde2d\",\n      \"token\": \"storage\"\n    },\n    {\n      \"foreground\": \"61ce3c\",\n      \"token\": \"string\"\n    },\n    {\n      \"foreground\": \"61ce3c\",\n      \"token\": \"meta.verbatim\"\n    },\n    {\n      \"foreground\": \"8da6ce\",\n      \"token\": \"support\"\n    },\n    {\n      \"foreground\": \"ab2a1d\",\n      \"fontStyle\": \"italic\",\n      \"token\": \"invalid.deprecated\"\n    },\n    {\n      \"foreground\": \"f8f8f8\",\n      \"background\": \"9d1e15\",\n      \"token\": \"invalid.illegal\"\n    },\n    {\n      \"foreground\": \"ff6400\",\n      \"fontStyle\": \"italic\",\n      \"token\": \"entity.other.inherited-class\"\n    },\n    {\n      \"foreground\": \"ff6400\",\n      \"token\": \"string constant.other.placeholder\"\n    },\n    {\n      \"foreground\": \"becde6\",\n      \"token\": \"meta.function-call.py\"\n    },\n    {\n      \"foreground\": \"7f90aa\",\n      \"token\": \"meta.tag\"\n    },\n    {\n      \"foreground\": \"7f90aa\",\n      \"token\": \"meta.tag entity\"\n    },\n    {\n      \"foreground\": \"ffffff\",\n      \"token\": \"entity.name.section\"\n    },\n    {\n      \"foreground\": \"d5e0f3\",\n      \"token\": \"keyword.type.variant\"\n    },\n    {\n      \"foreground\": \"f8f8f8\",\n      \"token\": \"source.ocaml keyword.operator.symbol\"\n    },\n    {\n      \"foreground\": \"8da6ce\",\n      \"token\": \"source.ocaml keyword.operator.symbol.infix\"\n    },\n    {\n      \"foreground\": \"8da6ce\",\n      \"token\": \"source.ocaml keyword.operator.symbol.prefix\"\n    },\n    {\n      \"fontStyle\": \"underline\",\n      \"token\": \"source.ocaml keyword.operator.symbol.infix.floating-point\"\n    },\n    {\n      \"fontStyle\": \"underline\",\n      \"token\": \"source.ocaml keyword.operator.symbol.prefix.floating-point\"\n    },\n    {\n      \"fontStyle\": \"underline\",\n      \"token\": \"source.ocaml constant.numeric.floating-point\"\n    },\n    {\n      \"background\": \"ffffff08\",\n      \"token\": \"text.tex.latex meta.function.environment\"\n    },\n    {\n      \"background\": \"7a96fa08\",\n      \"token\": \"text.tex.latex meta.function.environment meta.function.environment\"\n    },\n    {\n      \"foreground\": \"fbde2d\",\n      \"token\": \"text.tex.latex support.function\"\n    },\n    {\n      \"foreground\": \"ffffff\",\n      \"token\": \"source.plist string.unquoted\"\n    },\n    {\n      \"foreground\": \"ffffff\",\n      \"token\": \"source.plist keyword.operator\"\n    }\n  ],\n  \"colors\": {\n    \"editor.foreground\": \"#e0e2e4\",\n    \"editor.background\": \"#222222\",\n    \"editor.selectionBackground\": \"#253B76\",\n    \"editor.lineHighlightBackground\": \"#FFFFFF1F\",\n    \"editorCursor.foreground\": \"#FFFFFFA6\",\n    \"editorWhitespace.foreground\": \"#FFFFFF40\"\n  }\n}\n"
  },
  {
    "path": "web/playground/src/workbench/prql-syntax.js",
    "content": "const TRANSFORMS = [\n  \"aggregate\",\n  \"append\",\n  \"derive\",\n  \"filter\",\n  \"from_text\",\n  \"from\",\n  \"group\",\n  \"join\",\n  \"select\",\n  \"sort\",\n  \"take\",\n  \"union\",\n  \"window\",\n];\nconst MODULES = [\"date\", \"math\", \"text\"];\nconst BUILTIN_FUNCTIONS = [\"case\"]; // \"in\", \"as\"\nconst KEYWORDS = [\"let\", \"prql\"];\nconst LITERALS = [\"null\", \"true\", \"false\"];\n\nconst def = {\n  // Set defaultToken to invalid to see what you do not tokenize yet\n  // defaultToken: 'invalid',\n\n  keywords: [\n    ...TRANSFORMS,\n    ...MODULES,\n    ...BUILTIN_FUNCTIONS,\n    ...KEYWORDS,\n    ...LITERALS,\n  ],\n\n  operators: [\n    \"+\",\n    \"-\",\n    \"*\",\n    \"/\",\n    \"//\",\n    \"%\",\n    // \"**\",\n    \"==\",\n    \"!=\",\n    \"->\",\n    \"=>\",\n    \">\",\n    \"<\",\n    \">=\",\n    \"<=\",\n    \"~=\",\n    \"&&\",\n    \"||\",\n    \"??\",\n  ],\n\n  // The main tokenizer for our languages\n  tokenizer: {\n    root: [\n      // comments\n      { include: \"@comment\" },\n\n      // named-args\n      [/(\\w+)\\s*:/, { cases: { $1: \"key\" } }],\n\n      // identifiers and keywords\n      [\n        /[a-z_$][\\w$]*/,\n        { cases: { \"@keywords\": \"keyword\", \"@default\": \"identifier\" } },\n      ],\n\n      // whitespace\n      { include: \"@whitespace\" },\n\n      // delimiters\n      [/[()[\\]]/, \"@brackets\"],\n\n      // numbers\n      // Slightly modified from https://stackoverflow.com/a/23872060/3064736;\n      // it requires a number after a decimal point, so ranges appear as\n      // ranges.\n      // We disallow a leading word character, so that we don't highlight\n      // a number in `foo_1`,\n      // We allow underscores, a bit more liberally than PRQL, which doesn't\n      // allow them at the start or end (but that's difficult to express with\n      // regex; contributions welcome).\n      [/[+-]?[^\\w](([\\d_]+(\\.[\\d_]+])?)|(\\.[\\d_]+))/, \"number\"],\n\n      // strings\n      [/\"([^\"\\\\]|\\\\.)*$/, \"string.invalid\"], // non-terminated string\n      [/\"/, { token: \"string.quote\", bracket: \"@open\", next: \"@string\" }],\n\n      // characters\n      [/'[^\\\\']'/, \"string\"],\n    ],\n\n    comment: [[/#.*/, \"comment\"]],\n\n    string: [\n      [/[^\\\\\"]+/, \"string\"],\n      [/\"/, { token: \"string.quote\", bracket: \"@close\", next: \"@pop\" }],\n    ],\n\n    whitespace: [\n      [/[ \\t\\r\\n]+/, \"white\"],\n      [/\\/\\*/, \"comment\", \"@comment\"],\n      [/\\/\\/.*$/, \"comment\"],\n    ],\n  },\n};\nexport default def;\n"
  },
  {
    "path": "web/playground/vite.config.js",
    "content": "import { defineConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\nimport wasm from \"vite-plugin-wasm\";\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  build: {\n    target: \"esnext\",\n  },\n  base: \"/playground/playground\",\n  plugins: [react(), wasm()],\n});\n"
  },
  {
    "path": "web/prql-codemirror-demo/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "web/prql-codemirror-demo/README.md",
    "content": "# CodeMirror with PRQL demo\n\nThis is a demo of CodeMirror with PRQL. We don't have any published `lang-prql`\nor `prql-lezer` package yet.\n\n## Instructions\n\nBuild `prql-lezer` then copy the files from `/grammars/prql-lezer/dist/` to\n`src/lang-prql/prql-lezer/`.\n\n```\nmkdir src/lang-prql/prql-lezer\ncd ../../grammars/prql-lezer/\nnpm run build\ncp dist/* ../../web/prql-codemirror-demo/src/lang-prql/prql-lezer/\ncd ../../web/prql-codemirror-demo/\nnpm run dev\n```\n"
  },
  {
    "path": "web/prql-codemirror-demo/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>CodeMirror with PRQL</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "web/prql-codemirror-demo/package.json",
    "content": "{\n  \"name\": \"prql-codemirror-demo\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.3.2\",\n    \"vite\": \"^6.4.1\"\n  },\n  \"dependencies\": {\n    \"@codemirror/autocomplete\": \"^6.11.0\",\n    \"@codemirror/commands\": \"^6.3.0\",\n    \"@codemirror/language\": \"^6.9.2\",\n    \"@codemirror/lint\": \"^6.4.2\",\n    \"@codemirror/search\": \"^6.5.4\",\n    \"@codemirror/theme-one-dark\": \"^6.1.2\",\n    \"@codemirror/view\": \"^6.22.0\"\n  }\n}\n"
  },
  {
    "path": "web/prql-codemirror-demo/src/codemirror.ts",
    "content": "// This file a copy of https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts\nimport {\n  keymap,\n  highlightSpecialChars,\n  drawSelection,\n  highlightActiveLine,\n  dropCursor,\n  rectangularSelection,\n  crosshairCursor,\n  lineNumbers,\n  highlightActiveLineGutter,\n} from \"@codemirror/view\";\nimport { Extension, EditorState } from \"@codemirror/state\";\nimport {\n  defaultHighlightStyle,\n  syntaxHighlighting,\n  indentOnInput,\n  bracketMatching,\n  foldGutter,\n  foldKeymap,\n} from \"@codemirror/language\";\n\nimport { defaultKeymap, history, historyKeymap } from \"@codemirror/commands\";\nimport { searchKeymap, highlightSelectionMatches } from \"@codemirror/search\";\nimport {\n  autocompletion,\n  completionKeymap,\n  closeBrackets,\n  closeBracketsKeymap,\n} from \"@codemirror/autocomplete\";\nimport { lintKeymap } from \"@codemirror/lint\";\n\n// (The superfluous function calls around the list of extensions work\n// around current limitations in tree-shaking software.)\n\n/// This is an extension value that just pulls together a number of\n/// extensions that you might want in a basic editor. It is meant as a\n/// convenient helper to quickly set up CodeMirror without installing\n/// and importing a lot of separate packages.\n///\n/// Specifically, it includes...\n///\n///  - [the default command bindings](#commands.defaultKeymap)\n///  - [line numbers](#view.lineNumbers)\n///  - [special character highlighting](#view.highlightSpecialChars)\n///  - [the undo history](#commands.history)\n///  - [a fold gutter](#language.foldGutter)\n///  - [custom selection drawing](#view.drawSelection)\n///  - [drop cursor](#view.dropCursor)\n///  - [multiple selections](#state.EditorState^allowMultipleSelections)\n///  - [reindentation on input](#language.indentOnInput)\n///  - [the default highlight style](#language.defaultHighlightStyle) (as fallback)\n///  - [bracket matching](#language.bracketMatching)\n///  - [bracket closing](#autocomplete.closeBrackets)\n///  - [autocompletion](#autocomplete.autocompletion)\n///  - [rectangular selection](#view.rectangularSelection) and [crosshair cursor](#view.crosshairCursor)\n///  - [active line highlighting](#view.highlightActiveLine)\n///  - [active line gutter highlighting](#view.highlightActiveLineGutter)\n///  - [selection match highlighting](#search.highlightSelectionMatches)\n///  - [search](#search.searchKeymap)\n///  - [linting](#lint.lintKeymap)\n///\n/// (You'll probably want to add some language package to your setup\n/// too.)\n///\n/// This extension does not allow customization. The idea is that,\n/// once you decide you want to configure your editor more precisely,\n/// you take this package's source (which is just a bunch of imports\n/// and an array literal), copy it into your own code, and adjust it\n/// as desired.\nexport const basicSetup: Extension = (() => [\n  lineNumbers(),\n  highlightActiveLineGutter(),\n  highlightSpecialChars(),\n  history(),\n  foldGutter(),\n  drawSelection(),\n  dropCursor(),\n  EditorState.allowMultipleSelections.of(true),\n  indentOnInput(),\n  syntaxHighlighting(defaultHighlightStyle, { fallback: true }),\n  bracketMatching(),\n  closeBrackets(),\n  autocompletion(),\n  rectangularSelection(),\n  crosshairCursor(),\n  highlightActiveLine(),\n  highlightSelectionMatches(),\n  keymap.of([\n    ...closeBracketsKeymap,\n    ...defaultKeymap,\n    ...searchKeymap,\n    ...historyKeymap,\n    ...foldKeymap,\n    ...completionKeymap,\n    ...lintKeymap,\n  ]),\n])();\n\n/// A minimal set of extensions to create a functional editor. Only\n/// includes [the default keymap](#commands.defaultKeymap), [undo\n/// history](#commands.history), [special character\n/// highlighting](#view.highlightSpecialChars), [custom selection\n/// drawing](#view.drawSelection), and [default highlight\n/// style](#language.defaultHighlightStyle).\nexport const minimalSetup: Extension = (() => [\n  highlightSpecialChars(),\n  history(),\n  drawSelection(),\n  syntaxHighlighting(defaultHighlightStyle, { fallback: true }),\n  keymap.of([...defaultKeymap, ...historyKeymap]),\n])();\n\nexport { EditorView } from \"@codemirror/view\";\n"
  },
  {
    "path": "web/prql-codemirror-demo/src/lang-prql/complete.ts",
    "content": "import {\n  Completion,\n  completeFromList,\n  ifNotIn,\n  snippetCompletion as snip,\n} from \"@codemirror/autocomplete\";\n\nconst dontComplete = [\n  \"Comment\",\n  \"Docblock\",\n  \"String\",\n  \"FString\",\n  \"RString\",\n  \"SString\",\n];\n\nconst globals: readonly Completion[] = [\"false\", \"null\", \"true\"]\n  .map((n) => ({ label: n, type: \"constant\" }))\n  .concat(\n    [\n      \"bool\",\n      \"float\",\n      \"int\",\n      \"int8\",\n      \"int16\",\n      \"int32\",\n      \"int64\",\n      \"int128\",\n      \"text\",\n      \"date\",\n      \"time\",\n      \"timestamp\",\n    ].map((n) => ({ label: n, type: \"type\" })),\n  )\n  .concat(\n    [\n      // aggregate-functions\n      \"any\",\n      \"average\",\n      \"concat_array\",\n      \"count\",\n      \"every\",\n      \"max\",\n      \"min\",\n      \"stddev\",\n      \"sum\",\n      // file-reading-functions\n      \"read_csv\",\n      \"read_json\",\n      \"read_parquet\",\n      // list-functions\n      \"all\",\n      \"map\",\n      \"zip\",\n      \"_eq\",\n      \"_is_null\",\n      // misc-functions\n      \"from_text\",\n      // window-functions\n      \"lag\",\n      \"lead\",\n      \"first\",\n      \"last\",\n      \"rank\",\n      \"rank_dense\",\n      \"row_number\",\n    ].map((n) => ({ label: n, type: \"function\" })),\n  )\n  .concat(\n    [\n      \"aggregate\",\n      \"derive\",\n      \"filter\",\n      \"from\",\n      \"group\",\n      \"join\",\n      \"select\",\n      \"sort\",\n      \"take\",\n      \"window\",\n    ].map((n) => ({ label: n, type: \"keyword\" })),\n  )\n  .concat([\"date\", \"math\", \"text\"].map((n) => ({ label: n, type: \"module\" })))\n  .concat([\"std\"].map((n) => ({ label: n, type: \"namespace\" })));\n\nexport const snippets: readonly Completion[] = [\n  snip(\n    \"from ${table_table}\\nselect {${column_name}}\\nfilter ${column_name} == ${condition}\\ntake {amount}\",\n    {\n      label: \"from-select-filter-take\",\n      detail: \"snippet\",\n      type: \"text\",\n    },\n  ),\n];\n\n/// Autocompletion for built-in PRQL globals and keywords.\nexport const globalCompletion = ifNotIn(\n  dontComplete,\n  completeFromList(globals.concat(snippets)),\n);\n"
  },
  {
    "path": "web/prql-codemirror-demo/src/lang-prql/prql.ts",
    "content": "import { parser } from \"./prql-lezer\";\nimport {\n  delimitedIndent,\n  indentNodeProp,\n  foldNodeProp,\n  foldInside,\n  LRLanguage,\n  LanguageSupport,\n} from \"@codemirror/language\";\nimport { globalCompletion } from \"./complete\";\n\n/// A language provider based on the [Lezer PRQL\n/// parser](https://github.com/PRQL/prql/tree/main/grammars/prql-lezer), extended with\n/// highlighting and indentation information.\nexport const prqlLanguage = LRLanguage.define({\n  name: \"prql\",\n  parser: parser.configure({\n    props: [\n      indentNodeProp.add({\n        TupleExpression: delimitedIndent({ closing: \"}\" }),\n        ArrayExpression: delimitedIndent({ closing: \"]\" }),\n      }),\n      foldNodeProp.add({\n        \"ArrayExpression TupleExpression\": foldInside,\n      }),\n    ],\n  }),\n  languageData: {\n    closeBrackets: {\n      brackets: [\"(\", \"[\", \"{\", \"'\", '\"', \"'''\", '\"\"\"'],\n      stringPrefixes: [\"f\", \"r\", \"s\"],\n    },\n    commentTokens: { line: \"#\" },\n  },\n});\n\n/// PRQL language support.\nexport function prql() {\n  return new LanguageSupport(prqlLanguage, [\n    prqlLanguage.data.of({ autocomplete: globalCompletion }),\n  ]);\n}\n"
  },
  {
    "path": "web/prql-codemirror-demo/src/main.ts",
    "content": "import \"./style.css\";\nimport { EditorView, basicSetup } from \"./codemirror.ts\";\nimport { prql } from \"./lang-prql/prql.ts\";\nimport { oneDark } from \"@codemirror/theme-one-dark\";\n\ndocument.querySelector<HTMLDivElement>(\"#app\")!.innerHTML = `\n  <div>\n    <h1>PRQL CodeMirror demo</h1>\n    <p>\n      PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement.\n    </p>\n    <div id=\"editor\"></div>\n  </div>\n`;\n\nlet doc = `from invoices\nfilter invoice_date >= @1970-01-16\nderive {\n  transaction_fees = 0.8,\n  income = total - transaction_fees\n}\nfilter income > 1\ngroup customer_id (\n  aggregate {\n    average total,\n    sum_income = sum income,\n    ct = count total,\n  }\n)\nsort {-sum_income}\ntake 10\njoin c=customers (==customer_id)\nderive name = f\"{c.last_name}, {c.first_name}\"\nselect {\n  c.customer_id, name, sum_income\n}\nderive db_version = s\"version()\"\n`;\n\nnew EditorView({\n  doc,\n  extensions: [basicSetup, oneDark, prql()],\n  parent: document.getElementById(\"editor\")!,\n});\n"
  },
  {
    "path": "web/prql-codemirror-demo/src/style.css",
    "content": ":root {\n  color-scheme: light dark;\n  color: rgba(255, 255, 255, 0.87);\n  background-color: #242424;\n}\n\n.cm-editor {\n  max-height: 400px;\n  border: 1px solid silver;\n}\n"
  },
  {
    "path": "web/prql-codemirror-demo/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "web/prql-codemirror-demo/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "web/website/.gitignore",
    "content": "public/\n\n.hugo_build.lock\n"
  },
  {
    "path": "web/website/README.md",
    "content": "# PRQL Website\n\nA [hugo](https://gohugo.io/) website with PRQL theme, which uses minimal\n[water.css](https://watercss.kognise.dev/) styling.\n\nServe:\n\n```sh\ncd website\nhugo server\n```\n\nTo add pages, just add files in `content/` directory.\n"
  },
  {
    "path": "web/website/archetypes/default.md",
    "content": "---\ntitle: \"{{ replace .Name \"-\" \" \" | title }}\"\ndate: {{ .Date }}\ndraft: true\n---\n"
  },
  {
    "path": "web/website/config.yaml",
    "content": "baseURL: \"https://prql-lang.org/\"\nlanguageCode: en-us\ntitle: PRQL\ntheme: prql-theme\n\ndisableKinds:\n  - taxonomy\n  - term\n\nenableGitInfo: true\n\nmenu:\n  nav:\n    # TODO: Can we use the info from pages directly? https://gohugo.io/templates/menu-templates/#menu-entries-from-the-pages-front-matter\n    - identifier: Docs\n      name: Docs\n      url: /book/\n      weight: 1\n    - identifier: Playground\n      name: Playground\n      url: /playground/\n      weight: 2\n    - identifier: Roadmap\n      name: Roadmap\n      url: /roadmap/\n      weight: 3\n    - identifier: Posts\n      name: Posts\n      url: /posts/\n      weight: 4\n    - identifier: FAQ\n      name: FAQ\n      url: /faq/\n      weight: 5\n\nparams:\n  contribute:\n    subtitle: |\n      Contribute to PRQL — we're a welcoming community, and there are lots of ways to make an impact:\n\n    list:\n      - text:\n          Are there people whose opinion you respect who might be interested in\n          PRQL? Send them a link to PRQL.\n      - text:\n          \"Know Rust (or want to learn!)? [Contribute to our\n          compiler](https://prql-lang.org/book/project/contributing/#compiler)\"\n      - text:\n          \"Familiar with web? Spot a mistake? [Improve the website design or\n          copy](https://prql-lang.org/book/project/contributing/#marketing)\"\n      - text:\n          \"Interested in helping people explore their data? [Help improve the\n          playground](https://github.com/PRQL/prql/tree/main/web/playground)\"\n      - text:\n          \"Is SQL your mother-tongue? [Show us SQL queries which translate well\n          /\n          badly](https://prql-lang.org/book/project/contributing/#language-design).\n          We are looking for any use-cases that expose a poor design choice, a\n          need of a feature, or a sharp edge of the language.\"\n    button:\n      text: Contribute\n      link: https://prql-lang.org/book/project/contributing/\n  follow:\n    subtitle: \"Follow us:\"\n    list:\n      - text: Star our GitHub repo [PRQL/prql](https://github.com/PRQL/prql)\n      - text: Join our [Discord](https://discord.com/invite/eQcfaCmsNc)\n      - text: Follow us on [Twitter](https://twitter.com/prql_lang)\n  description: PRQL is a modern language for transforming data\n  images:\n    - static/img/favicon-32x32.png\n  title: PRQL\n\n  license: \"© 2022-2024, PRQL Developers. Apache 2.0 Licensed\"\n\ngoogleAnalytics: G-LQJDD599T4\n"
  },
  {
    "path": "web/website/content/_index.md",
    "content": "---\n####################### General #########################\nlayout: home\ntitle: PRQL\n\nhero_section:\n  enable: true\n  heading: \"PRQL is a modern language for transforming data\"\n  bottom_text: \"— a simple, powerful, pipelined SQL replacement\"\n  button:\n    enable: false\n    link: https://prql-lang.org/book/\n    label: \"Reference\"\n  # the PRQL example is defined in data/examples/hero.yaml\n\nwhy_prql_section:\n  enable: true\n  title: \"Why PRQL?\"\n  items:\n    - title: For data engineers\n      content:\n        - PRQL is concise, with abstractions such as variables & functions\n        - PRQL is database agnostic, compiling to many dialects of SQL\n        - PRQL isn't limiting — it can contain embedded SQL where necessary\n        - PRQL has bindings to most major languages _(and more are in progress)_\n        - PRQL allows for column lineage and type inspection _(in progress)_\n    - title: For analysts\n      content:\n        - PRQL is ergonomic for data exploration — for example, commenting out a\n          filter, or a column in a list, maintains a valid query\n        - PRQL is simple, and easy to understand, with a small number of\n          powerful concepts\n        - PRQL allows for powerful autocomplete, type-checking, and helpful\n          error messages _(in progress)_\n    - title: For tools\n      content:\n        - PRQL's vision is a foundation to build on; we're open-source and will\n          never have a commercial product\n        - PRQL is growing into a single secular standard which tools can target\n        - PRQL is easy for machines to read & write\n    - title: For HackerNews enthusiasts\n      content:\n        - The PRQL compiler is written in Rust\n        - We talk about \"orthogonal language features\" a lot\n\nshowcase_section:\n  enable: true\n  title: \"Showcase\"\n  content:\n    - PRQL consists of a curated set of orthogonal transformations, which are\n      combined together to form a pipeline. That makes it easy to compose and\n      extend queries. The language also benefits from modern features, such\n      syntax for dates, ranges and f-strings as well as functions, type checking\n      and better null handling.\n  buttons:\n    - link: \"/playground/\"\n      label: \"Playground\"\n    - link: \"/book/\"\n      label: \"Book\"\n  examples:\n    # The examples are defined in data/examples/, this list just defines their order.\n    - basic\n    - friendly-syntax\n    - orthogonal\n    - expressions\n    - f-strings\n    - windows\n    - functions\n    - top-n\n    - s-strings\n    - joins\n    - null-handling\n    - dialects\n\n    # Currently excluded because it's lots of text\n    # prql: |\n    #   # Check out how much simpler this is relative to the SQL...\n\n    #   let track_plays = (                     # Assign with `let`\n    #     from plays\n    #     group [track] (\n    #       aggregate [\n    #         total = count,\n    #         unfinished = sum is_unfinished,\n    #         started = sum is_started,\n    #       ]\n    #     )\n    #   )\n\nprinciples_section:\n  enable: true\n  title: \"Principles\"\n  items:\n    - title: \"Pipelined\"\n      main_text: \"A PRQL query is a linear pipeline of transformations\"\n      content:\n        Each line of the query is a transformation of the previous line’s\n        result. This makes it easy to read, and simple to write.\n\n    - title: \"Simple\"\n      main_text:\n        \"PRQL serves both sophisticated engineers and analysts without coding\n        experience.\"\n      content:\n        By providing a small number of powerful & orthogonal primitives, queries\n        are simple and composable — there's only one way of expressing each\n        operation. We can eschew the debt that SQL has built up.\n\n    - title: \"Open\"\n      main_text: \"PRQL is open-source, with an open community\"\n      content:\n        PRQL will always be fully open-source and will never have a commercial\n        product. By compiling to SQL, PRQL is compatible with most databases,\n        existing tools, and programming languages that manage SQL. We're a\n        welcoming community for users, contributors, and other projects.\n\n    - title: \"Extensible\"\n      main_text:\n        \"PRQL is designed to be extended, from functions to language bindings\"\n      content:\n        PRQL has abstractions which make it a great platform to build on. Its\n        explicit versioning allows changes without breaking\n        backward-compatibility. And in the cases where PRQL doesn't yet have an\n        implementation, it allows embedding SQL with s-strings.\n\n    - title: \"Analytical\"\n      main_text: \"PRQL's focus is analytical queries\"\n      content:\n        PRQL was originally designed to serve the growing need of writing\n        analytical queries, emphasizing data transformations, development speed,\n        and readability. We de-emphasize other SQL features such as inserting\n        data or transactions.\n\nvideos_section:\n  enable: true\n  title: \"Pipelines in action\"\n  items:\n    - youtube_id: IQRRsfavEic\n\ntools_section:\n  enable: true\n  title: \"Tools\"\n  sections:\n    - link: https://prql-lang.org/playground/\n      label: \"Playground\"\n      text:\n        \"Online in-browser playground that compiles PRQL to SQL as you type.\"\n\n    - link: https://pyprql.readthedocs.io/\n      label: \"pyprql\"\n      text: |\n        Provides Jupyter/IPython cell magic and Pandas accessor.\n\n        `pip install pyprql`\n\n    - link: https://crates.io/crates/prqlc\n      label: \"prqlc\"\n      text: |\n        A CLI for PRQL compiler, written in Rust.\n\n        `cargo install prqlc`\n\n        `brew install prqlc`\n\n        `winget install prqlc`\n\nintegrations_section:\n  enable: true\n  title: \"Integrations\"\n  sections:\n    - label: \"ClickHouse\"\n      link: https://clickhouse.com/docs/en/guides/developer/alternative-query-languages\n      text: |\n        ClickHouse natively supports PRQL with\n\n        `SET dialect = 'prql'`\n\n    - label: \"Jupyter/IPython\"\n      link: https://pyprql.readthedocs.io/en/latest/magic_readme.html\n      text:\n        \"pyprql contains a Jupyter extension, which executes a PRQL cell against\n        a database. It can also set up an in-memory DuckDB instance, populated\n        with a pandas DataFrame.\"\n\n    - label: \"DuckDB\"\n      link: https://github.com/ywelsch/duckdb-prql\n      text: A DuckDB extension to execute PRQL\n\n    - label: \"QStudio\"\n      link: https://www.timestored.com/qstudio/prql-ide\n      text:\n        \"QStudio is a SQL GUI that lets you browse tables, run SQL scripts, and\n        chart and export the results. QStudio runs on Windows, macOS and Linux,\n        and works with every popular database including mysql, postgresql,\n        mssql, kdb...\"\n\n    - label: \"Prefect\"\n      link: https://prql-lang.org/book/project/integrations/prefect.html\n      text: Add PRQL models to your Prefect workflows with a single function.\n\n    - label: Visual Studio Code\n      link: https://marketplace.visualstudio.com/items?itemName=prql-lang.prql-vscode\n      text: Extension with syntax highlighting and live SQL compilation.\n\n    - label: \"PostgreSQL\"\n      link: https://github.com/kaspermarstal/plprql\n      text: Write PRQL functions in PostgreSQL\n\n    - label: \"Databend\"\n      link: https://www.databend.com/blog/2024-04-03-databend-integrates-prql/\n      text: Databend natively supports PRQL\n\nbindings_section:\n  enable: true\n  title: \"Bindings\"\n  section_id: \"bindings\"\n  sections:\n    - link: https://pypi.org/project/prqlc\n      label: \"prqlc-python\"\n      text: Python bindings for prqlc.\n\n    - link: https://www.npmjs.com/package/prqlc\n      label: \"prqlc-js\"\n      text: \"JavaScript bindings for prqlc.\"\n\n    - link: https://CRAN.R-project.org/package=prqlr\n      label: \"prqlr\"\n      text: \"R bindings for prqlc.\"\n\n    - link: \"https://crates.io/crates/prqlc\"\n      label: \"prqlc\"\n      text: |\n        Compiler implementation, written in Rust. Compile, format & annotate PRQL queries.\n\n    - link: https://prql-lang.org/book/project/bindings/index.html\n      label: Others\n      text: |\n        Java, C, C++, Elixir, .NET, and PHP have unsupported or nascent bindings.\n\ntestimonials_section:\n  enable: true\n  title: \"What people are saying\"\n  # The testimonials are in data/testimonials.yaml.\n---\n"
  },
  {
    "path": "web/website/content/demos/ace-demo.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n<title>Ace with PRQL</title>\n<style>\n    #editor {\n        position: absolute;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        left: 0;\n    }\n</style>\n</head>\n<body>\n\n<div id=\"editor\"># https://ace.c9.io/\nfrom invoices\nfilter invoice_date >= @1970-01-16\nderive {\n  transaction_fees = 0.8,\n  income = total - transaction_fees\n}\nfilter income > 1\ngroup customer_id (\n  aggregate {\n    average total,\n    sum_income = sum income,\n    ct = count total,\n  }\n)\nsort {-sum_income}\ntake 10\njoin c=customers (==customer_id)\nderive name = f\"{c.last_name}, {c.first_name}\"\nselect {\n  c.customer_id, name, sum_income\n}\nderive db_version = s\"version()\"</div>\n\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/ace/1.31.2/ace.min.js\" integrity=\"sha512-4qIbBlcJOvbOmEB50FfnviJ9jOCen2yhi4skodkbTLU/uJJdULPxlX2R9Enf1oOBleUK9KD3fGmMcnItFQdNeA==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n<script>\n    var editor = ace.edit(\"editor\");\n    editor.setTheme(\"ace/theme/monokai\");\n    editor.session.setMode(\"ace/mode/prql\");\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "web/website/content/faq.md",
    "content": "---\ntitle: \"FAQ\"\n---\n\nHere are some of the most common questions we hear. Have something else you'd\nlike to ask? Pop by our [Discord](https://discord.com/invite/eQcfaCmsNc) and ask\naway!\n\n{{< faq \"Cool story Hansel, but what can I actually do with PRQL _now_?\" >}}\n\nPRQL is ready to use by the intrepid, either with our supported integrations, or\nwithin your own tools, using one of our supported language bindings. The easiest\nway is with our integrations:\n\n- **Prototype your PRQL queries** in the\n  [Playground](https://prql-lang.org/playground/) or the\n  [VS Code extension](https://marketplace.visualstudio.com/items?itemName=PRQL-lang.prql-vscode)\n  and copy/paste the resulting SQL into your database. It's not the perfect\n  workflow, but it's easy to get started.\n- **[Jupyter](https://pyprql.readthedocs.io/en/latest/magic_readme.html)**\n  allows writing PRQL in a Jupyter notebook or IPython repl, with a `%%prql`\n  magic. As well as connecting to existing DBs, our integration with DuckDB\n  enables querying pandas dataframes, CSVs & Parquet files, and writing the\n  output to a dataframe.\n- **[DuckDB extension](https://github.com/ywelsch/duckdb-prql)** — is a DuckDB\n  extension which allows querying a DuckDB instance with PRQL.\n\nIt's also possible to query PRQL from your code with our [bindings](/#bindings)\nfor R, Rust, Python & JS. For an example of using PRQL with DuckDB, check out\n[Querying with PRQL](https://eitsupi.github.io/querying-with-prql/).\n\n{{</ faq >}}\n\n{{< faq \"Something here reminds me of another project, did you take the idea from them?\" >}}\n\nYes, probably. We're standing on the shoulders of giants:\n\n- [dplyr](https://dplyr.tidyverse.org/) is a beautiful language for manipulating\n  data, in R. It's very similar to PRQL. It only works on in-memory R data.\n  - There's also [dbplyr](https://dbplyr.tidyverse.org/) which compiles a subset\n    of dplyr to SQL, though requires an R runtime.\n- [Kusto](https://learn.microsoft.com/en-us/kusto/query/tutorials/learn-common-operators?view=azure-data-explorer&preserve-view=true&pivots=azuredataexplorer)\n  is also a beautiful pipelined language, similar to PRQL. But it can only use\n  Kusto-compatible DBs.\n  <!-- We can add more articles by linking from works in the \"There are other similar piecs out there\" sentence -->\n- [Against SQL](https://www.scattered-thoughts.net/writing/against-sql/) gives a\n  fairly complete description of SQL's weaknesses, both for analytical and\n  transactional queries. [**@jamii**](https://github.com/jamii) consistently\n  writes insightful pieces, and it's worth sponsoring him for his updates. There\n  are\n  [other](https://buttondown.email/jaffray/archive/sql-scoping-is-surprisingly-subtle-and-semantic/)\n  similar pieces out there.\n- Julia's [DataPipes.jl](https://gitlab.com/aplavin/DataPipes.jl) &\n  [Chain.jl](https://github.com/jkrumbiegel/Chain.jl) demonstrate how effective\n  point-free pipelines can be, and how line breaks can work as pipes.\n- [OCaml](https://ocaml.org/)'s elegant and simple syntax serves as inspiration.\n\nAnd there are many projects similar to PRQL:\n\n- [Ecto](https://hexdocs.pm/ecto/Ecto.html#module-query) is a sophisticated ORM\n  library in Elixir which has pipelined queries as well as more traditional ORM\n  features.\n- [Morel](https://www.thestrangeloop.com/2021/morel-a-functional-query-language.html)\n  is a functional language for data, also with a pipeline concept. It doesn't\n  compile to SQL but states that it can access external data.\n- [Malloy](https://github.com/looker-open-source/malloy) from Looker &\n  [**@lloydtabb**](https://github.com/lloydtabb) in a new language which\n  combines a declarative syntax for querying with a modelling layer.\n- [EdgeDB](https://www.edgedb.com/) is an alternative to SQL focused on\n  traditional transactional workloads (as opposed to PRQL's focus on analytical\n  workloads). Their post\n  [We can do better than SQL](https://www.edgedb.com/blog/we-can-do-better-than-sql)\n  contains many of the criticisms of SQL that inspired PRQL.\n- [FunSQL.jl](https://github.com/MechanicalRabbit/FunSQL.jl) is a library in\n  Julia which compiles a nice query syntax to SQL. It requires a Julia runtime.\n- [LINQ](https://learn.microsoft.com/en-us/dotnet/csharp/linq/get-started/write-linq-queries),\n  is a pipelined language for the `.NET` ecosystem which can (mostly) compile to\n  SQL. It was one of the first languages to take this approach.\n- [Sift](https://github.com/RCHowell/Sift) is an experimental language which\n  heavily uses pipes and relational algebra.\n\n> If any of these descriptions can be improved, please feel free to PR changes.\n\n{{</ faq >}}\n\n{{< faq \"How is PRQL different from all the projects that SQL has defeated?\" >}}\n\nMany languages have attempted to replace SQL, and yet SQL has massively _grown_\nin usage and importance in the past decade. There are lots\n[of](https://twitter.com/seldo/status/1513599841355526145)\n[reasonable](https://benn.substack.com/p/has-sql-gone-too-far?s=r#footnote-anchor-2)\n[critiques](https://erikbern.com/2018/08/30/i-dont-want-to-learn-your-garbage-query-language.html)\non these attempts. So a reasonable question is \"Why are y'all building something\nthat many others have failed at?\". Some thoughts:\n\n- PRQL is open. It's not designed for a specific database. PRQL will always be\n  fully open-source. There will never be a commercial product. We'll never have\n  to balance profitability against compatibility, or try and expand up the stack\n  to justify a valuation. Whether someone is building a new tool or writing a\n  simple query — PRQL can be _more_ compatible across DBs than SQL.\n- PRQL is analytical. The biggest growth in SQL usage over the past decade has\n  been from querying large amounts of data, often from analytical DBs that are\n  specifically designed for this — with columnar storage and wide denormalized\n  tables. SQL carries a lot of unrelated baggage, and focusing on the analytical\n  use-case lets us make a better language.\n- PRQL is simple. There's often a tradeoff between power and accessibility\n  — e.g. rust is powerful vs. Excel is accessible — but there are also instances\n  where we can expand the frontier. PRQL's orthogonality is an example of\n  synthesizing this tradeoff — have a single `filter` rather than `WHERE` &\n  `HAVING` & `QUALIFY` brings both more power _and_ more accessibility.\n\nIn the same way that \"SQL was invented in the 1970s and therefore must be bad\"\nis questionable logic, \"`n` languages have tried and failed so therefore SQL\ncannot be improved.\" suffers a similar fallacy. SQL isn't bad because it's old.\nIt's bad because — in some cases — it's bad.\n\n{{</ faq >}}\n\n{{< faq \"Which databases does PRQL work with?\" >}}\n\nPRQL compiles to SQL, so it's compatible with any database that accepts SQL.\n\nA query's dialect can be explicitly specified, allowing for dialect-specific SQL\nto be generated. See the\n[Dialect docs](https://prql-lang.org/book/project/target.html) for more info;\nnote that there is currently very limited implementation of this, and most\ndialects' implementation are identical to a generic implementation.\n\n{{</ faq >}}\n\n{{< faq \"What's this `aggregate` function?\" >}}\n\n**...and why not just use `SELECT` & `GROUP BY`?**\n\nSQL uses `SELECT` for all of these:\n\n- Selecting or computing columns, without changing the shape of the data:\n\n  ```sql\n  SELECT x * 2 FROM y as two_x\n  ```\n\n- Reducing a column into a single row, with a reduction function:\n\n  ```sql\n  SELECT SUM(x) FROM y\n  ```\n\n- Reducing a column into groups, with a reduction function and a `GROUP BY`\n  function:\n\n  ```sql\n  SELECT SUM(x) FROM y GROUP BY z\n  ```\n\nThese are not orthogonal — `SELECT` does lots of different things depending on\nthe context. It's difficult for both people and machines to evaluate the shape\nof the output. It's easy to mix meanings and raise an error (e.g.\n`SELECT x, MIN(y) FROM z`).\n\nPRQL clearly delineates two operations with two transforms:\n\n- `select` — picking & calculating columns. These calculations always produce\n  exactly one output row for every input row.\n\n  ```prql\n  from employees\n  select name = f\"{first_name} {last_name}\"\n  ```\n\n- `aggregate` — reducing multiple rows to a single row, with a reduction\n  function like `sum` or `min`.\n\n  ```prql\n  from employees\n  aggregate [total_salary = sum salary]\n  ```\n\n`aggregate` can then be used in a `group` transform, where it has exactly the\nsame semantics on the group as it would on a whole table — another example of\nPRQL's orthogonality.\n\n```prql\nfrom employees\ngroup department (\n  aggregate [total_salary = sum salary]\n)\n```\n\nWhile you should be skeptical of new claims from new entrants\n[Hadley Wickham](https://twitter.com/hadleywickham), the developer of\n[Tidyverse](https://www.tidyverse.org/)\n[commented](https://news.ycombinator.com/item?id=30067406) in a discussion on\nPRQL:\n\n> FWIW the separate `group_by()` is one of my greatest design regrets with dplyr\n> — I wish I had made `by` a parameter of `summarise()`, `mutate()`, `filter()`\n> etc.\n\nFor more detail, check out the docs in the\n[PRQL Book](https://prql-lang.org/book/reference/stdlib/transforms/aggregate.html).\n\n{{</ faq >}}\n\n{{< faq \"Can PRQL write to databases?\" >}}\n\nPRQL is focused on analytical queries, so we don't currently support writing or\nmodifying data in databases. However, PRQL queries can be used to generate SQL\nstatements that write to databases. For example, surround the SQL output of a\nPRQL query in `CREATE OR REPLACE TABLE foo AS (...)`.\n\n{{</ faq >}}\n\n{{< faq \"Is it 'PRQL' or 'prql' or 'Prql'?\" >}}\n\nIt's `PRQL`, since it's a backronym! We name the repo and some libraries `prql`\nbecause of a strong convention around lowercase, but everywhere else we use\n`PRQL`.\n\n{{</ faq >}}\n\n{{< faq \"Where can I find the logos?\" >}}\n\nSee our [press materials](https://github.com/PRQL/prql-brand).\n\n{{</ faq >}}\n"
  },
  {
    "path": "web/website/content/playground.html",
    "content": "---\ntitle: \"Playground\"\nlayout: big_iframe\n---\n\n<iframe src=\"/playground/playground\"></iframe>\n"
  },
  {
    "path": "web/website/content/posts/2022-05-19-examples.md",
    "content": "---\ntitle: \"Examples\"\ndate: 2022-05-19\nlayout: article\nurl: examples\n---\n\n## A simple example\n\nHere's a fairly simple SQL query:\n\n```sql\nSELECT TOP 20\n  title,\n  country,\n  AVG(salary) AS average_salary,\n  SUM(salary) AS sum_salary,\n  AVG(salary + payroll_tax) AS average_gross_salary,\n  SUM(salary + payroll_tax) AS sum_gross_salary,\n  AVG(salary + payroll_tax + benefits_cost) AS average_gross_cost,\n  SUM(salary + payroll_tax + benefits_cost) AS sum_gross_cost,\n  COUNT(*) AS ct\nFROM\n  employees\nWHERE\n  start_date > DATE('2021-01-01')\n  AND salary + payroll_tax + benefits_cost > 0\nGROUP BY\n  title,\n  country\nHAVING\n  COUNT(*) > 2000\nORDER BY\n  sum_gross_cost,\n  country DESC\n```\n\nEven this simple query demonstrates some of the problems with SQL's lack of\nabstractions:\n\n- Unnecessary repetition — the calculations for each measure are repeated,\n  despite deriving from a previous measure. The repetition in the `WHERE` clause\n  obfuscates the meaning of the expression.\n- Functions have multiple operators — `HAVING` & `WHERE` are fundamentally\n  similar operations applied at different stages of the pipeline, but SQL's lack\n  of pipeline-based precedence requires it to have two different operators.\n- Operators have multiple functions — the `SELECT` operator both creates new\n  aggregations, and selects which columns to include.\n- Awkward syntax — when developing the query, commenting out the final line of\n  the `SELECT` list causes a syntax error because of how commas are handled, and\n  we need to repeat the columns in the `GROUP BY` clause in the `SELECT` list.\n\nHere's the same query with PRQL:\n\n```prql\nfrom employees                                # Each line transforms the previous result.\nfilter start_date > @2021-01-01               # Clear date syntax.\nderive {                                      # `derive` adds columns / variables.\n  gross_salary = salary + payroll_tax,\n  gross_cost = gross_salary + benefits_cost   # Variables can use other variables.\n}\nfilter gross_cost > 0\ngroup {title, country} (                      # `group` runs a pipeline over each group.\n  aggregate {                                 # `aggregate` reduces a column to a row.\n    average salary,\n    sum     salary,\n    average gross_salary,\n    sum     gross_salary,\n    average gross_cost,\n    sum_gross_cost = sum gross_cost,          # `=` sets a column name.\n    ct = count this,\n  }\n)\nsort {sum_gross_cost, -country}               # `-country` means descending order.\nfilter ct > 2_000\ntake 20\n```\n\nAs well as using variables to reduce unnecessary repetition, the query is also\nmore readable — it flows from top to bottom, each line representing a\ntransformation of the previous line's result. For example, `TOP 20` / `take 20`\nmodify the final result in both queries — but only PRQL represents it as the\nfinal transformation. And context is localized — the `aggregate` transform is\nimmediately wrapped in a `group` transform containing the columns to group by.\n\nWhile PRQL is designed for reading & writing by people, it's also much simpler\nfor code to construct or edit PRQL queries. In SQL, adding a filter to a query\ninvolves parsing the query to find and then modify the `WHERE` statement, or\nwrapping the existing query in a CTE. In PRQL, adding a filter just involves\nappending a `filter` transformation to the query.\n\nFor more examples, check out the [PRQL Book](https://prql-lang.org/book/).\n\n<!--\n\nTODO: This was a nice example for the proposal, but until we get functions which can contain column names,\nit doesn't compile, and so is confusing. When we get that working, we can re-enable it.\n\n## A more complex example\n\nHere's another SQL query, which calculates returns from prices on days with\nvalid prices.\n\n> The implemented version of PRQL supports some but not all these features.\n\n```sql\nWITH total_returns AS (\n  SELECT\n    date,\n    sec_id,\n    -- Can't use a `WHERE` clause, as it would affect the row that the `LAG` function referenced.\n    IF(is_valid_price, price_adjusted / LAG(price_adjusted, 1) OVER\n      (PARTITION BY sec_id ORDER BY date) - 1 + dividend_return, NULL) AS return_total,\n    IF(is_valid_price, price_adjusted_usd / LAG(price_adjusted_usd, 1) OVER\n      (PARTITION BY sec_id ORDER BY date) - 1 + dividend_return, NULL) AS return_usd,\n    IF(is_valid_price, price_adjusted / LAG(price_adjusted, 1) OVER\n      (PARTITION BY sec_id ORDER BY date) - 1 + dividend_return, NULL)\n      - interest_rate / 252 AS return_excess,\n    IF(is_valid_price, price_adjusted_usd / LAG(price_adjusted_usd, 1) OVER\n      (PARTITION BY sec_id ORDER BY date) - 1 + dividend_return, NULL)\n      - interest_rate / 252 AS return_usd_excess\n  FROM prices\n)\nSELECT\n  *,\n  return_total - (interest_rate / 252) AS return_excess,\n  EXP(SUM(LN(GREATEST(1 + return_total - (interest_rate / 252), 0.01))) OVER (ORDER BY date)) AS return_excess_index\nFROM total_returns\nJOIN interest_rates USING (date)\n```\n\n> This might seem like a convoluted example, but it's taken from a real query.\n> Indeed, it's also simpler and smaller than the full logic — note that it\n> starts from `price_adjusted`, whose logic had to be split into a previous\n> query to avoid the SQL becoming even less readable.\n\nHere's the same query with PRQL:\n\n```prql\nprql version:0.3 db:snowflake                         # PRQL version & database name.\n\nfunc excess x -> (x - interest_rate) / 252            # Functions are clean and simple.\nfunc if_valid x -> is_valid_price ? x : null\nfunc lag_day x -> group sec_id (                      # `group` is used for window partitions too\n  sort date\n  window (                                            # `window` runs a pipeline over each window\n    lag 1 x                                           # `lag 1 x` lags the `x` col by 1\n  )\n)\n\nfunc ret x -> x / (x | lag_day) - 1 + dividend_return\n\nfrom prices\njoin interest_rates (==date)\nselect [                                              # `select` only includes unnamed columns, unlike `derive`\n  return_total =      prices_adj   | ret | if_valid   # `|` can be used rather than newlines\n  return_usd =        prices_usd   | ret | if_valid\n  return_excess =     return_total | excess\n  return_usd_excess = return_usd   | excess\n  return_index = (                                    # No need for a CTE\n    return_total + 1\n    excess\n    greatest 0.01\n    ln\n    group sec_id (                                    # Complicated logic remains clear(er)\n      sort date\n      window ..current (                              # Rolling sum\n        sum\n      )\n    )\n    exp\n  )\n]\n```\n\nBecause we define the functions once rather than copying & pasting the code, we\nget all the benefits of encapsulation and extensibility — we have reliable &\ntested functions, whose purpose is explicit, which we can share across queries\nand between colleagues.\n\nWe needed a CTE in the SQL query, because the lack of variables would have\nrequired a nested window clause, which isn't allowed. With PRQL, our logic isn't\nconstrained by these arbitrary constraints — and is more compressed as a result.\n\nThe larger query demonstrates PRQL orthogonality. PRQL has fewer keywords\nthan SQL, and each of them does something specific and composable; for example:\n\n- `group` maps a pipeline over groups; whether in a table context — `GROUP BY`\n  in SQL — or within a `window` — `PARTITION BY` in SQL.\n- A transform in context of a `group` does the same transformation to the group\n  as it would to the table — for example finding the rolling sum of a column.\n  For more on this equivalence, check out [`group`'s\n  documentation](https://prql-lang.org/book/reference/transforms/group.html)\n- `filter` filters out rows which don't meet a condition. That can be before an\n  aggregate — `WHERE` in SQL — after an aggregate — `HAVING` in SQL — or within\n  a `window` — `QUALIFY` in SQL. -->\n"
  },
  {
    "path": "web/website/content/posts/2023-01-07-functional-relations.md",
    "content": "---\ntitle: A functional approach to relational queries\ndate: 2023-01-07\nauthors: [\"Aljaž Mur Eržen\"]\nlayout: article\ntoc: true\nurl: functional-relations\n---\n\nI believe that many of the ideas from functional programming are very suitable\nfor a query language. To make a case for it, I'll briefly explain a few\nfeatures, adapt their syntax, and show how they are used in PRQL.\n\n## Fancy functional features\n\nFunctional programming is an old-school programming paradigm that is gradually\ngaining traction within a few common languages that are designed with procedural\nparadigm in mind.\n\nThe many aspects of the paradigm have been discussed extensively, so I'll try to\nbe brief. If you've already read them all, I suggest you skip to the next\nsection.\n\n### Pure functions\n\nThis is the core aspect that allows us to do most of the magic later. When we\nsay \"pure function\", we just mean the mathematical function and not \"a\nprocedure\" or \"a method\". To be more precise:\n\n- Pure functions have no side effects. This means that no global state is\n  altered. The function result is the only output that the function produces.\n- Pure functions produce the same output given the same args. The output does\n  not depend on any external information; for example the current time or a\n  random number generator.\n\nThe most obvious effect is that for a pure function `my_function`, this:\n\n```js\nres1 = my_function(arg);\nres2 = my_function(arg);\nres3 = my_function(arg);\n```\n\n... is equivalent to:\n\n```js\nres1 = my_function(arg);\nres2 = res1;\nres3 = res2;\n```\n\nBecause `my_function` is guaranteed to return the same result when given `arg`,\nwe can skip calling `my_function` for the second and the third time because we\nalready know what the output will be.\n\nThis may seem like a minor point, but we're just getting started.\n\n### First-class citizens\n\nFunctions being \"first-class citizens\" means that they are treated the same as\nany other value; they can be stored in a variable, they can be passed to another\nfunction as an argument or even be used in binary operations.\n\n```js\nfunction double(x) {\n  return 2 * x;\n}\n\nlet preprocess = double;\n\nmy_array = [4, 5, 6];\n\nmy_array.map(preprocess);\n// yields [8, 10, 12]\n```\n\nHere, we've stored function `double` in variable `preprocess` and then passed\nthat to method `Array.map`, which applies a given function to each of the array\nelements.\n\n### Currying\n\nNamed after Haskell Curry, currying is an implicit act of converting a function\ncall with missing arguments into a new function.\n\n```js\nfunction add(x, y) {\n  return x + y;\n}\nlet add_one = add(1);\n```\n\nBecause we didn't specify `y` parameter of `add` function, the result is a new\nfunction that is still waiting for the last argument. It is equivalent to\ndefining add_one like this:\n\n```js\nfunction add_one(y) {\n  return add(1, y);\n}\n```\n\n### Implicit function call\n\nLet's suppose that we have a pure function that doesn't take any arguments:\n\n```js\nfunction my_function() {\n  return 42;\n}\n```\n\nBecause it is a pure function, it cannot depend on anything other than its\nnon-existing arguments. Another way to phrase it, this is a constant expression.\n\nAnd as such, it might as well be defined as one:\n\n```js\nlet my_var = 42;\n```\n\nIn conventional programming languages[^1], there is a difference between using\n`my_function()` and `my_var`. The first one evaluates the expression at the call\nsite, while the second one evaluates it at the declaration site. It is also\npossible to express just `my_function`, a reference to a function that can be\ncalled without arguments.\n\nBut if all functions are pure, we generalize by saying that all three cases are\nequivalent.\n\nTo make this work, let's say that `my_function` is \"implicitly invoked\" just as\nit would have been expressed as `my_function()`.\n\nAlso, if we declare all functions to be pure it doesn't matter exactly _when_\nthey are evaluated. So let's give the compiler the authority to make an informed\nguess about _when_ and make `my_var` and `my_function()` semantically\nequivalent.\n\n[^1]:\n    Let's say that conventional languages are the first 15 from this list:\n    <https://survey.stackoverflow.co/2022/#most-popular-technologies-language-prof>\n\n## Syntax, surly shinier\n\nFirst of all, let's change the function call to this:\n\n```prql\n(my_function arg1 arg2 arg3)\n```\n\nIt may look strange because the function name is within the parenthesis and\nthere are no commas between arguments. But there are benefits to this syntax.\n\nFirstly, when the call has no arguments, the parenthesis can be omitted:\n\n```prql\n(my_function) == my_function\n```\n\nwhich feels very natural because of the similar behavior with expressions. As\nintended, a function call with no argument and a plain reference to the function\nare expressed with the same syntax.\n\nLet's extend this behavior and allow bare function calls in a few places where\nthey won't become ambiguous:\n\n```prql\n# a list:\n[my_function arg1 arg2 arg3, my_function arg4 arg5 arg6]\n# a declaration:\nlet res = my_function arg1 arg2 arg3\n```\n\nSecondly, I'd argue that currying looks natural.\n\n```prql\nlet curry = my_function arg1 arg2\nlet res = curry arg3\n\n# or inline:\nlet res = (my_function arg1 arg2) arg3\n```\n\nNow let's go one step further and introduce a \"pipe\" operator. It applies its\nleft operand as an argument to the right operand:\n\n```prql\narg3 | my_function arg1 arg2\n```\n\nThis is especially useful for chaining function calls. If we declare `div`,\n`floor` and `mul` as common arithmetic functions, the function call syntax\nstarts to make sense:\n\n```prql\n12 | div 5 | floor | mul 5\n```\n\n## Running real relations\n\nUp to this point, we've discussed language design in the abstract, without\nknowing what it will be used for. Designing for the sake of \"language being\ncool\" is fine, but we do have a use for it, which means that the language must\nbe designed to be the right tool for the job.\n\nThe job, in the case of PRQL, is querying databases.\n\nThe core unit of data here is a \"relation\": an ordered set of rows, each of\nwhich contains entries for each of the columns. (More people are likely to be\nfamiliar with a \"table\", which is a relation stored in a DB.)\n\nQueries operate in a static environment that contains references to database\ntables and a library of standard functions. The important keyword here is\n\"static\" by which I mean \"global immutable variables\" that can be referenced\nfrom pure functions [^2].\n\nPRQL currently supports most of the features described in previous sections,\nfocused on the innovative function call syntax.\n\n[^2]:\n    Many functions in SQL are not pure, and we don't have a plan on\n    [how to deal with that](https://github.com/PRQL/prql/issues/1111).\n\n### Basics\n\nA basic operation on a relation would be:\n\n```prql\n(take 3 albums)\n# or with a pipeline:\n(albums | take 3)\n```\n\n... where `take` is a function and `albums` is a static relation. This does\nexactly what it sounds like: it limits the result to the first n rows. One could\nsay it selects the top n rows.\n\nIn actual PRQL queries, we have some rules regarding references to tables, which\nI'll talk about it some other time. For now, let's just say that for referencing\ntables (i.e. static relations) use the function `from`. It has the bonus that it\nmakes queries look a lot more like SQL:\n\n```prql\n(from albums | take 3)\n```\n\nTo make querying easier, we have some neat name resolution rules that allow\nfunction arguments to refer to each other. In practice, it allows referring to\ncolumns of a relation in function calls:\n\n```prql\n(select [title, artist_id] default_db.albums)\n# and with a pipeline:\n(from albums | select {title, artist_id})\n```\n\nAll these queries can be simplified to an expression of relations and scalars.\nIn PRQL, we call such expressions \"Relational Queries\" or RQ for short. It is an\nintermediate representation of prqlc and can be translated to SQL and executed\non basically any relational database.\n\nThis is the gist of how to express SQL queries with a functional language. At\nthis stage a curious reader might ask \"can PRQL express any SQL query?\" to which\nI'd say: almost. Another might be wondering \"but is it less cumbersome and more\nconsistent?\" and I'd reply \"very much, yes\", but it depends on whom you ask. And\nsomeone might say \"why functional?\" to which I say \"exactly the question I was\nwaiting for!\".\n\nThere are a few quick benefits I want to get out of the way:\n\n- Pipelines make the flow of the query \"top-to-bottom\". An earlier part of a the\n  pipeline will always be a valid query, regardless of what follows.\n- Functions on relations (transforms) are designed to be orthogonal. Each of\n  them has as few effects as possible and can be used at any point in the\n  pipeline (except for `from`).\n\nBut there is more.\n\n### Aggregate\n\nAt this point, I have to introduce `aggregate`. It takes a relation and produces\na single row using some aggregation function:\n\n```prql\n(from albums | aggregate {n_albums = count})\n```\n\nWhat happens if we don't specify the leading relation?\n\n```prql\n(aggregate {n_albums = count})\n```\n\nBecause `aggregate` is missing an argument, currying kicks in, and the whole\nexpression evaluates to a function that is still waiting for a relation.\n\nIn SQL it is common to use GROUP BY when aggregating. If you think about it, it\nessentially separates the relation into groups using some criteria and then\napplies `aggregate` to each of the groups. This is exactly how PRQL expressed\nit:\n\n```prql\nfrom albums | group artist_id (aggregate {n_albums = count})\n```\n\nThis is a lot for one line, so let's unveil new syntactic conveniences: a new\nline is a pipe operator and the top-level pipeline does not need parenthesis.\nI'll also add a new transform at the back, don't worry about it.\n\n```prql\nfrom albums\ngroup artist_id (\n    aggregate {n_albums = count}\n)\nfilter n_albums > 3\n```\n\nNotice how `group` operates on the whole relation it gets from `from` and that\n`aggregate` is passed to `group` as a function to be applied to each of the\ngroups. `aggregate` then converts each of the groups into a single row and\nreturns that to `group` that composes these rows back together. This can now be\npassed on to `filter`, which will remove any rows that don't match its\ncondition.\n\nIn SQL, a similar expression would have these four parts:\n\n- projection in SELECT,\n- an aggregation function that implicitly triggers aggregation,\n- GROUP BY clause,\n- HAVING clause.\n\nThese parts are entangled syntactically and semantically into one feature we\nunderstand as GROUPING.\n\nThe beautiful realization is that these are 3 different operations that are\nhappening and that when separated they don't need to be associated with each\nother. For example `filter` is more commonly expressed as WHERE and `group` has\nother uses than `aggregate`.\n\nBut to separate these core relational operations, we need a way to express\naggregate as a function. This is why the functional paradigm fits the relational\ndata model.\n\n### Distinct\n\nA common question when learning SQL is \"how do I select the row where column x\nis smallest?\". It has many variations, but there are two ways of doing it:\n\n```sql\n-- option 1\nSELECT x, y, z FROM tab ORDER BY x LIMIT 1\n\n-- option 2\nSELECT x, y, z FROM tab WHERE x = (SELECT min(x) FROM tab)\n```\n\n[A follow-up question](https://stackoverflow.com/questions/3800551/select-first-row-in-each-group-by-group)\nwould be \"how do I select the row where column x is smallest, for each group\nover y?\". This seems like a similar problem but the solution in SQL is\nsurprisingly different:\n\n```sql\n-- option 1 (unsupported in some dialects)\nSELECT DISTINCT ON (y) x, y, z FROM tab ORDER BY y, x, z;\n\n-- option 2  (supported by most dialects)\nWITH summary AS (\n    SELECT x, y, z,\n        ROW_NUMBER() OVER(PARTITION BY y ORDER BY x) AS rank\n    FROM tab)\nSELECT * FROM summary WHERE rank = 1\n```\n\nNow break the query down into core operations. Essentially we want to do the\nsame thing we did before, but performed in groups by `y`. Before we used SQL\nthat can be expressed as `sort x | take 1` (which evaluates to a function), so\nnow surely this should work:\n\n```prql\nfrom tab\ngroup y (sort x | take 1)\n```\n\nAnd it does. You can go and test this out in the\n[PRQL playground](https://prql-lang.org/playground/).\n\nAnother variation of the question would be \"how do I select a row, for each\ngroup over all columns?\". If you phrase it differently \"group by all columns and\nthen take one row from each group\". Or another way: \"select distinct values of\nall columns\".\n\n```prql\nfrom tab\ngroup tab.* (take 1)\n```\n\nAs you can see, our language has many shortcuts for expressing operations such\nas DISTINCT. This is convenient for us humans, but it's not a good base for a\nrelational query language.\n\nI hope my point is clear: relational query language benefits a lot by separating\noperations into orthogonal transforms[^3]. These transforms are in most cases\npure functions that are easiest to express in a functional language.\n\n<!-- SQL, on the other hand, uses many keywords and syntactic constructs to\nexpress the most commonly used combinations of these transforms. This falls short\nwhen trying to express uncommon operations while not providing significantly\nshorter queries. -->\n\nIf you want to see more of what PRQL is capable of, come and check out\n[the project](https://github.com/PRQL/prql). It may not have monads (yet), but\nit's probably better than what you are forced to use now.\n\n[^3]:\n    Transforms in PRQL are not completely orthogonal. `select`, `derive`,\n    `aggregate` and `join` all manipulate relation columns. So in a sense, they\n    are much closer to each other than they are to `take`.\n\n## Appendix\n\n### PRQL support\n\nPRQL is a work in progress. It does not yet support all the features presented\nhere, namely:\n\n- `let` syntax for variable declarations,\n- inline currying - function call must start with a function name,\n- syntax for declaring tables.\n\nWe focused on the core features and left these out because they can be worked\naround.\n\nAlso, PRQL may not ever get all of these features, because the ideas in this\narticle are only my own and not necessarily of the whole PRQL core team.\n\n### In math, function call syntax is ambiguous\n\nIf you think about it, the function call syntax from math is kind of ambiguous.\nFor example, what does this mean:\n\n```math\na(b + 1)\n```\n\nIt could either be a call of function a, or it could be just multiplication\nwhere we omitted `*`. This is not a problem in conventional programming\nlanguages because they don't allow omitting the `*`.\n"
  },
  {
    "path": "web/website/content/posts/2023-01-27-prql-query.md",
    "content": "---\ntitle: Time tracking with pq\ndate: 2023-01-27\nauthors: [\"Aljaž Mur Eržen\"]\nlayout: article\n---\n\nSome time ago, I needed a time-tracking app that would be simple and fast. After\nlooking into a few heavy web applications, I settled with this one-liner:\n\n```\n# time_tracker.sh\necho $(date -u +\"%Y-%m-%dT%H:%M:%SZ\"),$1 >> ~/time-tracking.csv\n```\n\nI've made it a bit more sophisticated, but the core functionality is the same.\nThe the script is aliased to `tt`, so I can start or stop the timer in any open\nterminal by writing:\n\n```\n$ tt start\n$ tt stop\n```\n\nI've prefilled the resulting `~/time-tracking.csv` with a header, so it is ready\nto be analyzed.\n\n```\ntime,action\n2023-01-27T09:26:33Z,start\n2023-01-27T10:12:50Z,stop\n2023-01-27T12:54:04Z,start\n2023-01-27T15:12:07Z,stop\n```\n\nNow, I'd want to transform this data to show the total duration for each day.\n\nFor this I can use [prql-query](https://github.com/PRQL/prql-query), which is a\nCLI which can execute PRQL queries against database engines. At the time of\nwriting it supports duckdb and datafusion, but we can also connect to many other\nengines through these two.\n\nBut I don't need that today, plain duckdb will do:\n\n```\n$ pq --backend=duckdb \\\n     --from \"tt=~/time-tracking.csv\" \\\n     '{here comes the PRQL query below}'\n```\n\n```prql\n# function declaration that is a wrapper for substr SQL function\nlet substr = text start len -> s\"substr({text}, {start}, {len})\"\n\n\n# start of the pipeline\nfrom tt  # as declared in --from\n\n# compute a few new columns\nderive [\n    date = substr time 0 11,    # call the substr function to\n                                # extract date from column `time`\n    prev_action = lag 1 action, # lag column `action`\n    prev_time = lag 1 time,     # lag column `time`\n]\n\n# pick only rows that correspond to intervals that I want to track\nfilter action == \"stop\" and prev_action == \"start\"\n\n# for each date\ngroup date (\n    # sum durations of those intervals\n    aggregate [sec = sum s\"EXTRACT(EPOCH FROM {time - prev_time})\"]\n)\n\n# compute more columns\nderive [\n    hours = substr f\"00{sec / (60 * 60)}\" 0-2 2,\n    minutes = substr f\"00{(sec / 60) % 60}\" 0-2 2,\n    seconds = substr f\"00{sec % 60}\" 0-2 2,\n]\n\n# expose only date and pretty-printed duration\nselect [\n    date,\n    duration = f\"{hours}:{minutes}:{seconds}\"\n]\n```\n\nWhen run on the file above, prql-query produces this pretty table:\n\n```\n+------------+----------+\n| date       | duration |\n+------------+----------+\n| 2023-01-27 | 03:04:20 |\n+------------+----------+\n```\n\nThe full script implementation adds some conveniences like error handling and\nbetter output formatting, but the core concept remains: simple CSV logging that\ncan be queried with PRQL.\n"
  },
  {
    "path": "web/website/content/posts/2023-01-28-format-pretty-reports/_index.md",
    "content": "---\ntitle: Format pretty reports\ndate: 2023-01-28\nauthors: [\"richb-hanover\"]\nlayout: article\ntoc: false\n---\n\n> I can no longer bring myself to write bare SQL - PRQL makes building queries\n> so easy. So here's how I use PRQL _functions_ and _aliases_ for naming\n> variables to \"pretty up\" a SQL report.\n\nI have an SQLite database with a table named `PropertyData` and I want to show\nthe change of column `App_Total2020` and `App_Total2021` across years,\ndisplaying their values along with their percent change.\n\n## Functions\n\nOf course, property values are in dollars. So I could simply display them as\n`450000`. But they are more compelling if they're written as `$450,000`. Writing\nthe SQL code and format strings for each column would be tedious and\nerror-prone. PRQL allows me to create a `dollars` function and then use it\nmultiple times:\n\n```prql\n# dollars displays a numeric value as whole dollars with commas\nlet dollars = d -> s\"\"\"printf(\"$%,d\",{d})\"\"\"\n\nselect {\n    (dollars App_Total2020),\n    (dollars App_Total2021),\n}\n```\n\nI also want to compute the percent change between values. It's easy to create a\n`percent_diff` function:\n\n```prql\n# percent_diff computes the amount (percent) the new differs from old\nlet percent_diff = old new -> 100.0*( new - old ) / old\n```\n\nOne final function: the `percent_diff` function returns a floating point number\nwith many digits after the decimal place. I only want to display one place in my\nresults, with a trailing `%`. So I wrote a `format_percent` function that uses a\n`printf()` to format the value.\n\n```prql\n# format_percent prints a floating point number with \"%\"\nlet format_percent = v -> s'printf(\"%1.1f%\", {v})'\n```\n\n## Column Headings\n\nUse a PRQL _alias_ to assign each column a nice name. This becomes its column\nheading. The examples above might be:\n\n```prql\nselect {\n    Appraisal2020 = (dollars App_Total2020),\n   `Appraisal 2021` = (dollars App_Total2021),\n}\n```\n\nNote how the second example puts the column heading in backticks to preserve\nspaces.\n\n## Excluding certain columns\n\nI want to sort results by the (numeric) percent change, but I don't want to\ndisplay that percentage value (with multiple decimal places) in the final table.\nSo I split the query into pieces: the first `select` collects all the necessary\ncolumns, adding a new column using `percent_diff`. The query then sorts the\nvalues and passes those results to a second `select` that's responsible for\nformatting the column headings and contents (using aliases and\n`format_percent`).\n\n## Putting it all together\n\nHere is my workflow for a typical query using the tools listed below: [^1]\n\n- Enter the PRQL query in the VS Code editor. Use a `.prql` suffix for the file.\n- Open the PRQL VS Code extension (Ctl-Shift-P, or Cmd-Shift-P on Mac). It'll\n  appear and display the compiled SQL in a second pane on the right.\n- Copy the SQL from the right pane and paste it into the DBM program. Run the\n  query. That's it!\n\nHere's the PRQL for this example, followed by the result from my database.\n_Note:_ Paste the PRQL query below into the\n[Playground](https://prql-lang.org/playground/) to see what the PRQL compiler\nproduces.\n\n```prql\n# dollars displays a numeric value as dollars with commas\nlet dollars = d -> s\"\"\"printf('$%,d',{d})\"\"\"\n\n# percent_diff computes the amount (percent) the new differs from old\nlet percent_diff = old new -> 100.0*( new - old ) / old\n\n# format_percent prints a floating point number with \"%\"\nlet format_percent = v -> s\"printf('%1.1f%', {v})\"\n\n# Step 1: First calculate important columns\nfrom PropertyData\nselect {\n    Map, Lot,\n    App_Total2020,\n    App_Total2021,\n    pct_change = (percent_diff App_Total2020 App_Total2021),\n}\n\n# Step 2: Sort the resulting table by pct_change\nsort {-pct_change}\n\n# Step 3: Format the column headings and contents\nselect {\n    Map, Lot,\n    Appraisal2020 = (dollars App_Total2020),\n   `Appraisal 2021` = (dollars App_Total2021),\n   `Percent Change` = (format_percent pct_change),\n}\ntake 20\n```\n\nThe image below shows the result of that query:\n\n- the column headings match the _aliases_ of the second `select` statement,\n- the `dollars` function formats values with `$` and `,` as expected,\n- the `percent_diff` function computes the percent change between the _old_ and\n  _new_ values,\n- the `format_percent` function formats the value with a single decimal place\n  and appends a `%`.\n\n![First rows](./query_result.png)\n\n[^1]:\n    **My Tools:** I use [Visual Studio Code](https://code.visualstudio.com/) to\n    maintain a folder of PRQL queries for re-use. I use the\n    [PRQL VS Code extension](https://marketplace.visualstudio.com/items?itemName=PRQL-lang.prql-vscode)\n    to compile PRQL into SQL. My data is in a SQLite database, and I use\n    [DB Browser for SQLite](https://sqlitebrowser.org/) to run queries.\n"
  },
  {
    "path": "web/website/content/posts/2023-02-02-one-year/_index.md",
    "content": "---\ntitle: One year of PRQL\ndate: 2023-02-02\nlayout: article\ntoc: true\n---\n\nA year ago, we posted\n[a proposal for a modern SQL replacement](https://news.ycombinator.com/item?id=30060784)\nto Hacker News. It immediately sparked the interest of many people dealing with\ndata. The project grew a community of people who are now developing the\nlanguage, tooling, and the idea of a modern relational language. Since then\nwe've opened 1577 issues & PRs on [our main repo](https://github.com/PRQL/prql),\nsubmitted 4211 comments, and made 1176 commits.\n\nThe number of stars is skyrocketing every time the project appears on Hacker\nNews, which we believe to be an indicator that people are eager to adopt the\nlanguage if the tooling is made accessible enough.\n\n![](FQ9QSOo.png)\n\n## Where are we?\n\nLanguage design & development in the last year have been focused on these areas:\n\n- design of basic\n  [transforms](https://prql-lang.org/book/reference/stdlib/transforms/) and\n  their [interactions](https://github.com/PRQL/prql/issues/300),\n\n- fundamentals of how [functions](https://github.com/PRQL/prql/issues/444) and\n  pipelines are [evaluated](#define-functional-semantics).\n\n- small quality-of-life language features (e.g. syntax for\n  [f-strings, dates, coalesce operator](https://prql-lang.org/book/reference/syntax/),\n  [case](https://github.com/PRQL/prql/issues/504)),\n\nPRQL is now in a state where it can greatly improve the developer experience for\nwriting complex analytical queries, but it does require a bit of fiddling to set\nup in your environment.\n\nIn the coming year, we are aiming to improve that by providing a dbt plugin and\nintegrations for tools like Rill-developer, Metabase, and DataGrip. Read more\nabout our plans and ambitions in [the roadmap](https://prql-lang.org/roadmap/).\n\n## How are people using it?\n\nFor data analytics at [SuperSimple](https://gosupersimple.com/):\n\n> We've been using PRQL under the hood to power complex analytics workflows at\n> Supersimple for more than 6 months now. The speed of iteration and response to\n> user feedback has been amazing during this period!\n>\n> PRQL is what I think SQL should have been like from day 1 and adopting it has\n> likely literally saved us months.\n\nFor making\n[pretty reports with SQLite](https://prql-lang.org/posts/2023-01-28-format-pretty-reports/).\n\nFor quick, readable\n[scripts in the command line](https://prql-lang.org/posts/2023-01-27-prql-query/).\n\n## What have you missed?\n\nIn the past year, the community has created many things, some of which have not\nbeen noticed as much as they should have been. In the summary of the year, we\nwant to fix that and put a spotlight on the amazing work that was done.\n\n### Playground\n\nWe have a [Playground](https://prql-lang.org/playground/) that can compile and\nexecute PRQL queries in-browser. It's using prqlc and DuckDB, both compiled to\nWASM modules.\n\n![PRQL Playground](URpCf29.png)\n\nCurrently, this is the best way to see how the relation is manipulated instantly\nas you type the query.\n\n### VS Code extension\n\nExtension that provides syntax highlighting and compiled SQL within your editor.\n\n![VS Code extension](7cpDySb.png)\n\n### prql-query\n\nCLI tool that uses DuckDB and DataFusion to execute PRQL queries from your\nterminal. Useful for wrangling your CSV and parquet files on the go.\n\n![pq](ncVXken.png)\n\n### Define functional semantics\n\nGiven the initial proposal of the language, we constructed consistent semantics\nof how functions in PRQL work, what they can express and how can they be abused.\n\nTo keep this post brief, we'll expose a single snippet of what's possible and\ninvite you to read more in\n[a recent post](https://prql-lang.org/functional-relations/).\n\n```prql\nlet take_oldest = n rel -> (\n    rel\n    sort [-invoice_date]\n    take n\n)\n\nfrom invoices\ntake_oldest 3\n```\n\n> Function `take_oldest` is used in the main pipeline, just any other regular\n> transform.\n\n### Relational Query\n\nThe design of prqlc strives to have a complexity bottleneck with an intermediate\nrepresentation named\n[Relation Query](https://docs.rs/prqlc/latest/prqlc/ir/rq/index.html) or RQ for\nshort. Think of it as equivalent to a [Substrait plan](https://substrait.io/).\n\nIts goal is the ability to express any operation possible in SQL while\ncontaining as few constructs as possible. This makes it easy to implement\nbackends that compile RQ to SQL or any other language or library dealing with\nrelations or dataframes.\n\n![](GXLvoXn.png)\n\n> Note how prqlc inferred the structure of the table we are selecting from. It\n> knows that it must contain columns `billing_city` and `total`, but also notes\n> that there may be many other columns.\n"
  },
  {
    "path": "web/website/content/posts/2023-03-14-pi-day.md",
    "content": "---\ntitle: Calculate the digits of Pi with DuckDB and PRQL\ndate: 2023-03-14\nauthors: [\"Tobias Brandt\"]\nlayout: article\ntoc: true\n---\n\n_TL;DR: PRQL recently added a `loop` construct which makes it Turing Complete\nand allows doing cool things like calculating Pi right in your database._\n\n## Background\n\nLast week saw the 0.6 release of [PRQL](https://prql-lang.org) which brought\nwith it the capability to express Recursive CTEs in PRQL. \"Recursive\" CTEs\naren't actually truly recursive in the sense that that term is usually used,\nrather they use a \"recursive\" (i.e. self-referential) syntax to provide a\nlooping construct in SQL.\n\n[PRQL](https://prql-lang.org) is a modern, functional query language for\ntransforming data. One of its goals is to simplify working with data wherever\nyou can currently use SQL. As such it compiles to SQL while making available\nmodern ergonomics such as\n[f-strings](https://prql-lang.org/book/reference/syntax/f-strings.html), as well\nas not so modern features such as\n[functions](https://prql-lang.org/book/reference/declarations/functions.html).\n\nGiven that the underlying semantics of Recursive CTEs are really about iteration\nor \"looping\", we have called this feature `loop` in PRQL.\n\n## Introducing `loop`\n\nRecursive CTEs in SQL consist of two parts, an `initial_query` and an\n`update_query`. First the `initial_query` is executed and then the rows produced\nare fed to `update_query` which is applied to the result set. The `update_query`\nis then iteratively applied to the rows produced in the last iteration until no\nmore rows are produced, at which point iteration stops. (For a great review of\nthis as well as some interesting proposals to extend the semantics of Recursive\nCTEs in SQL, see the paper\n[\"A Fix for the Fixation on Fixpoints\" by Denis Hirn and Torsten Grust](https://www.cidrdb.org/cidr2023/papers/p14-hirn.pdf).)\n\nThis behavior can be expressed with following pseudo-code:\n\n```python\ndef loop(step, initial_query):\n    result = []\n    current = initial_query()\n    while current is not empty:\n        result = append(result, current)\n        current = update_query(current)\n\n    return result\n```\n\nThe minimal `loop` example from the documentation in the\n[PRQL book](https://prql-lang.org/book/reference/stdlib/transforms/loop.html)\nis:\n\n```prql\nfrom_text format:json '[{\"n\": 1 }]'\nloop (\n    select n = n+1\n    filter n<=3\n)\n```\n\nHere we use a PRQL utility function `from_text` to conveniently turn a JSON\nrepresentation of some example data into a SQL table (`from_text` currently also\naccepts CSV input).\n\nInitially a row with `{\"n\":1}` is fed in. Then the update query is applied to\nthis which in this case just increments `n` by one and filters the result set to\nonly the rows where `n` is less than or equal to three. This produces `{\"n\":2}`\nin the next step and `{\"n\":3}` after that. On the next iteration `{\"n\":4}` is\nproduced but that is eliminated by the filter condition. Since that leaves no\nnew rows, iteration stops.\n\nIf you try the query above in the\n[PRQL Playground](https://prql-lang.org/playground), the result set you get (in\nthe \"output.arrow\" tab) is:\n\n| n   |\n| --- |\n| 1   |\n| 2   |\n| 3   |\n\n## Fibonacci Numbers\n\nInspired by this, let's try to calculate Fibonacci numbers which are often one\nof the first examples when recursion is introduced:\n\n```elm=\nfrom_text format:json '[{\"a\":1, \"b\":1}]'\nloop (\n    derive b_new = a + b\n    select [a=b, b=b_new]\n)\ntake 7\n```\n\nwhich produces the first 7 Fibonacci numbers.\n\n| a   |\n| --- |\n| 1   |\n| 1   |\n| 2   |\n| 3   |\n| 5   |\n| 8   |\n| 13  |\n\nYou might have noticed that we didn't actually include a `filter` in our loop\nthis time. Instead we relied on the fact that DuckDB produces results lazily and\nsince we only took 7 numbers it only produced what we needed.\n\nNow, let's set ourselves a bigger challenge.\n\n## Calculating the digits of pi\n\nMarch 14th is written on American calendars as 3/14 which reminds us of the\nfirst three digits of Pi=3.1415926535... . Therefore this day is commonly known\nas Pi day. In order to celebrate Pi Day 2023 and the recent release of `loop` in\nPRQL 0.6, why don't we try to calculate the digits of Pi in PRQL!\n\nFor our query engine we will use [DuckDB](https://duckdb.org/) because it is\nreally fast and has many modern features that make it ideally suited for the\nkinds of analytical queries that PRQL is targeting. There is even a\n[duckdb-prql](https://github.com/ywelsch/duckdb-prql) extension which allows you\nto write PRQL queries right inside DuckDB!\n\nWe follow the algorithms in the paper\n[\"Unbounded Spigot Algorithms for the Digits of Pi\" by Jeremy Gibbons](https://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/spigot.pdf),\nin particular Rabinowitz and Wagon's Spigot Algorithm. While it is not the most\nefficient algorithm presented in the paper, it has the advantage that it uses\nonly standard data types while the more efficient algorithms rely on unbounded\ninteger types which are not available in many database systems.\n\nFor my implementation, I adapted the Python implementation by John Burkardt\nfound\n[here](https://people.sc.fsu.edu/~jburkardt/py_src/pi_spigot/pi_spigot.py). A\nbit of care had to be taken in porting the implementation to PRQL, as PRQL like\nSQL, is stateless so I introduced another state variable `k` to track the\nposition inside the body of the loop. In order to ensure that all values of `k`\nare handled in the query we define a PRQL function called `loop_steps`. The\nability to define functions in PRQL is one of its key features and I feel the\nquery below really shows the power that the composability of functions brings to\nPRQL. PRQL is a functional language with features such as currying. For more\ndetails see our previous post\n[A functional approach to relational queries](https://prql-lang.org/functional-relations/).\n\nThe resulting PRQL query is the following:\n\n```prql\nprql target:sql.duckdb\n\nlet config = (\n    from_text format:json '[{\"num_digits\":50}]'\n    derive {\n        array_len = (10*num_digits)/3,\n        calc_len = 1+4,\n        loop_len = array_len + calc_len,\n    }\n)\n\nlet loop_steps = step_0 step_i step_1 step_2 step_3 other -> case [\n    k==0 => step_0,\n    1 <= k and k <= array_len => step_i,\n    k==array_len+1 => step_1,\n    k==array_len+2 => step_2,\n    k==array_len+3 => step_3,\n    true => other,\n]\n\nlet q_steps = step_q9 step_q10 step_j2 step_jg2 other -> case [\n    q==9 => step_q9,\n    q==10 => step_q10,\n    j==2 => step_j2,\n    j>2 => step_jg2,\n    true => other,\n]\n\n\nfrom config\nselect [\n    num_digits,\n    array_len,\n    loop_len,\n    j = 0,\n    k = 0,\n    q = 0,\n    a = s\"[2 for i in range({array_len})]\",\n    nines = 0,\n    predigit = 0,\n    output = '',\n]\nloop (\n    filter j < num_digits + 1\n    derive [\n      j_new = case [k==0 => j+1, true => j],\n      k_new = (k+1) % loop_len,\n      q_step_i = (10*s\"{a}[{k}]\"+q*(array_len-k+1))/(2*(array_len-k)+1),\n      q_new = loop_steps 0 q_step_i (q/10) q q q,\n\n      a_step_i = s\"[CASE WHEN i=={k} THEN (10*{a}[i]+{q}*({array_len}-i+1))%(2*({array_len}-i)+1) ELSE {a}[i] END for i in generate_series(1,{array_len})]\",\n      a_step_1 = s\"[CASE WHEN i=={array_len} THEN {q}%10 ELSE {a}[i] END for i in generate_series(1,{array_len})]\",\n      a_new = loop_steps a a_step_i a_step_1 a a a,\n\n      nines_new = loop_steps nines nines nines (q_steps (nines+1) 0 nines nines nines) (case [q!=9 and q!=10 and nines!=0 => 0, true => nines]) nines,\n      predigit_new = loop_steps predigit predigit predigit (q_steps predigit 0 q q q) predigit predigit,\n\n      output_step_2 = (q_steps '' s\"({predigit}+1)::string || repeat('0', {nines})\" s\"{predigit}::string || '.'\" s\"{predigit}::string\" ''),\n      output_step_3 = (case [q!=9 and q!=10 and nines!=0 => s\"repeat('9', {nines})\", true => '']),\n      output_new = loop_steps '' '' '' output_step_2 output_step_3 '',\n    ]\n    select {\n        num_digits,\n        array_len,\n        loop_len,\n        j = j_new,\n        k = k_new,\n        q = q_new,\n        a = a_new,\n        nines = nines_new,\n        predigit = predigit_new,\n        output = output_new,\n    }\n)\naggregate [pi=s\"string_agg({output}, '')\"]\n```\n\nGo ahead and run this query right now in your browser with our\n[Online PRQL Playground](https://www.prql-lang.org/playground)! There you can\nalso see the SQL that is produced in the `output.sql` tab (for you convenience\nreproduced in the Appendix below) as well as the result of the calculation in\nthe `output.arrow` tab.\n\nThe output you see will be something like the following:\n\n```\n┌────────────────────────────────────────────────────┐\n│                         pi                         │\n│                      varchar                       │\n├────────────────────────────────────────────────────┤\n│ 3.141592653589793238462643383279502884197169399375 │\n└────────────────────────────────────────────────────┘\n```\n\nWhy don't you play around with the `num_digits` parameter to try and see how\nmany digits you can get on your laptop? (Unfortunately this algorithm is quite a\nbit more inefficient than the equivalent Python implementation as Recursive CTEs\ncurrently don't allow for state to be kept outside of the result set.)\n\nSomething you might have noticed is that there are some expressions surrounded\nby `s\"\"`. This is called an\n[s-string](https://prql-lang.org/book/reference/syntax/s-strings.html) (s for\nSQL) and allows us to include raw SQL inside our PRQL queries. We use this to\ninclude features from DuckDB that haven't made it into PRQL yet, such as the\nPythonesque\n[list comprehensions](https://duckdb.org/docs/sql/functions/nested.html#list-comprehension).\n\nThe `loop` functionality in PRQL is brand new and is marked as _experimental_\nand we will be working to stabilise the feature and iterate on the design to\nmake it as easy and useful as possible. We felt that it was exciting enough to\nshare it at this stage (and in time for Pi Day) and make it available for you to\ntry out and play around with. PRQL is developed completely in the open so let us\nknow your use cases so that we can make PRQL the best tool for the data\nchallenges that you face.\n\n## Conclusion\n\nRecursive CTEs in SQL make SQL Turing Complete and the same should hold for PRQL\nnow that `loop` is included. As we saw above, not all algorithms are easily\nexpressed in this paradigm, so while theoretically they could be, whether they\nshould be is another question. However I hope this example demonstrates that\n`loop` brings great power to PRQL and in follow up posts I will demonstrate how\nwe can use this to do tree and graph traversals which do come up in practice in\nthe kind of analytical data work that PRQL is made for. After that I will also\nlook at online algorithms such as moving averages an online gradient descent, so\nbe sure to come back for those!\n\n## Appendix\n\nThe following is the SQL query that was produced and run in DuckDB:\n\n```sql\nWITH table_0 AS (\n  SELECT\n    50 AS num_digits\n),\nconfig AS (\n  SELECT\n    num_digits,\n    10 * num_digits / 3 AS array_len,\n    5 AS calc_len,\n    10 * num_digits / 3 + 5 AS loop_len\n  FROM\n    table_0 AS table_1\n),\ntable_6 AS (\n  WITH RECURSIVE loop AS (\n    SELECT\n      num_digits,\n      array_len,\n      loop_len,\n      0 AS _expr_0,\n      0 AS _expr_1,\n      0 AS _expr_2,\n      [2 for i in range(array_len)] AS _expr_3,\n      0 AS _expr_4,\n      0 AS _expr_5,\n      '' AS _expr_6\n    FROM\n      config\n    UNION\n    ALL\n    SELECT\n      num_digits,\n      array_len,\n      loop_len,\n      _expr_12 AS _expr_15,\n      _expr_11 AS _expr_16,\n      _expr_10 AS _expr_17,\n      _expr_9 AS _expr_18,\n      _expr_8 AS _expr_19,\n      _expr_7 AS _expr_20,\n      CASE\n        WHEN _expr_1 = 0 THEN ''\n        WHEN 1 <= _expr_1\n        AND _expr_1 <= array_len THEN ''\n        WHEN _expr_1 = array_len + 1 THEN ''\n        WHEN _expr_1 = array_len + 2 THEN _expr_13\n        WHEN _expr_1 = array_len + 3 THEN _expr_14\n        ELSE ''\n      END\n    FROM\n      (\n        SELECT\n          num_digits,\n          array_len,\n          loop_len,\n          CASE\n            WHEN _expr_1 = 0 THEN _expr_5\n            WHEN 1 <= _expr_1\n            AND _expr_1 <= array_len THEN _expr_5\n            WHEN _expr_1 = array_len + 1 THEN _expr_5\n            WHEN _expr_1 = array_len + 2 THEN CASE\n              WHEN _expr_2 = 9 THEN _expr_5\n              WHEN _expr_2 = 10 THEN 0\n              WHEN _expr_0 = 2 THEN _expr_2\n              WHEN _expr_0 > 2 THEN _expr_2\n              ELSE _expr_2\n            END\n            WHEN _expr_1 = array_len + 3 THEN _expr_5\n            ELSE _expr_5\n          END AS _expr_7,\n          CASE\n            WHEN _expr_1 = 0 THEN _expr_4\n            WHEN 1 <= _expr_1\n            AND _expr_1 <= array_len THEN _expr_4\n            WHEN _expr_1 = array_len + 1 THEN _expr_4\n            WHEN _expr_1 = array_len + 2 THEN CASE\n              WHEN _expr_2 = 9 THEN _expr_4 + 1\n              WHEN _expr_2 = 10 THEN 0\n              WHEN _expr_0 = 2 THEN _expr_4\n              WHEN _expr_0 > 2 THEN _expr_4\n              ELSE _expr_4\n            END\n            WHEN _expr_1 = array_len + 3 THEN CASE\n              WHEN _expr_2 <> 9\n              AND _expr_2 <> 10\n              AND _expr_4 <> 0 THEN 0\n              ELSE _expr_4\n            END\n            ELSE _expr_4\n          END AS _expr_8,\n          CASE\n            WHEN _expr_1 = 0 THEN _expr_3\n            WHEN 1 <= _expr_1\n            AND _expr_1 <= array_len THEN [CASE WHEN i==_expr_1 THEN (10*_expr_3[i] + _expr_2 *(array_len - i + 1)\n          ) %(2 *(array_len - i) + 1)\n          ELSE _expr_3 [i]\n      END for i in generate_series(1, array_len) ]\n      WHEN _expr_1 = array_len + 1 THEN [CASE WHEN i==array_len THEN _expr_2%10 ELSE _expr_3[i]\n  END for i in generate_series(1, array_len) ]\n  WHEN _expr_1 = array_len + 2 THEN _expr_3\n  WHEN _expr_1 = array_len + 3 THEN _expr_3\n  ELSE _expr_3\nEND AS _expr_9,\nCASE\n  WHEN _expr_1 = 0 THEN 0\n  WHEN 1 <= _expr_1\n  AND _expr_1 <= array_len THEN (\n    10 * _expr_3 [_expr_1] + _expr_2 * (array_len - _expr_1 + 1)\n  ) / (2 * (array_len - _expr_1) + 1)\n  WHEN _expr_1 = array_len + 1 THEN _expr_2 / 10\n  WHEN _expr_1 = array_len + 2 THEN _expr_2\n  WHEN _expr_1 = array_len + 3 THEN _expr_2\n  ELSE _expr_2\nEND AS _expr_10,\n(_expr_1 + 1) % loop_len AS _expr_11,\nCASE\n  WHEN _expr_1 = 0 THEN _expr_0 + 1\n  ELSE _expr_0\nEND AS _expr_12,\n_expr_1,\nCASE\n  WHEN _expr_2 = 9 THEN ''\n  WHEN _expr_2 = 10 THEN (_expr_5 + 1) :: string || repeat('0', _expr_4)\n  WHEN _expr_0 = 2 THEN _expr_5 :: string || '.'\n  WHEN _expr_0 > 2 THEN _expr_5 :: string\n  ELSE ''\nEND AS _expr_13,\nCASE\n  WHEN _expr_2 <> 9\n  AND _expr_2 <> 10\n  AND _expr_4 <> 0 THEN repeat('9', _expr_4)\n  ELSE ''\nEND AS _expr_14,\n_expr_2,\n_expr_4\nFROM\n  loop AS table_2\nWHERE\n  _expr_0 < num_digits + 1\n) AS table_3\n)\nSELECT\n  *\nFROM\n  loop\n)\nSELECT\n  string_agg(_expr_6, '') AS pi\nFROM\n  table_6 AS table_5\n\n-- Generated by PRQL compiler version:0.6.1 (https://prql-lang.org)\n```\n"
  },
  {
    "path": "web/website/content/roadmap.md",
    "content": "---\ntitle: \"Roadmap\"\nurl: roadmap\n---\n\n> We're excited and inspired by the level of enthusiasm behind the project, both\n> from individual contributors and the broader community of users who are\n> unsatisfied with SQL. We currently have an working version for the intrepid\n> users.\n>\n> We're hoping we can build a beautiful language, integrations that are\n> approachable & powerful, and a vibrant community. Many projects have reached\n> the current stage and fallen, so this requires compounding on what we've done\n> so far.\n>\n> -- {{< cite >}}PRQL Developers{{< /cite >}}\n\n## Medium term\n\n{{< columns >}}\n\n#### Integrations\n\nPRQL is focused at the language layer, which means we can easily integrate with\nexisting tools & apps. Integrations will be the primary way that people can\nstart using PRQL day-to-day. At first, the most impactful initial integrations\nwill be tools that engineers use to build data pipelines, like\n[`dbt-prql`](https://github.com/PRQL/prql/issues/375).\n\n#### Standard library\n\nCurrently, the standard library is\n[quite limited](https://github.com/PRQL/prql/blob/main/prqlc/prqlc/src/semantic/std.prql).\nIt contains only basic arithmetic functions (`AVERAGE`, `SUM`) and lacks\nfunctions for string manipulation, date handling and many math functions. We're\nlooking to gradually introduce these as needed, and reduce the need for\ns-strings.\n\nOne challenge here is the variety of functionalities and syntax of target DBMSs;\ne.g. there's no standard regex function.\n\n#### Type system\n\nBecause PRQL is meant to be the querying interface of the database, a type\nsystem that can describe database schema as well as all intermediate results of\nthe queries is needed. We want it to provide clear distinctions between\ndifferent nullable and non-nullable values, and different kinds of containers\n(e.g. scalars vs. columns).\n\nCurrently PRQL compiles into SQL with no understanding of the underlying tables.\nWe plan to introduce database schema declarations into the language, so PRQL\ncompiler and tooling can enrich the developer experience with autocomplete and\nearly error messages.\n\nThe goal here is to catch all errors at PRQL compile time, instead of at the\ndatabase's PREPARE stage.\n\n<--->\n\n#### Friendliness\n\nCurrently the compiler output's friendliness is variable — sometimes it produces\nmuch better error messages than SQL, but sometimes they can be confusing.\n\nBoth bug reports of unfriendliness, and code contributions to improve them are\nwelcome; there's a\n[friendliness label.](https://github.com/PRQL/prql/issues?q=is%3Aissue+label%3Afriendliness+is%3Aopen)\n\n#### Developer ergonomics — LSP\n\nThe PRQL language can offer a vastly improved developer experience over SQL,\nboth when exploring data and building robust data pipelines. We'd like to offer\nautocomplete both for PRQL itself and for columns of the underlying database,\nbecause fast iteration cycle can drastically decrease frustrations caused by\nbanal misspellings.\n\nThis requires development across multiple dimensions — writing an\n[LSP server](https://langserver.org/), better support for typing in the\ncompiler, and possibly database cohesion.\n\nWhile PRQL compiler will never depend on a database to compile queries, an LSP\nserver could greatly help with generating type definitions from the information\nschema of a database.\n\n#### Query transparency\n\nPRQL's compiler already contains structured data about the query. We'd like to\noffer transparency to tools which use PRQL, so they can offer lineage\ninformation, such as which tables are queried, and a DAG of transformations for\neach column.\n\n{{< /columns >}}\n\n## Long term\n\n{{< columns >}}\n\n#### SQL-to-PRQL conversion\n\nWhile PRQL already allows for a gradual on-ramp — there's no need to switch\neverything to PRQL right away — it would also be useful to be able to convert\nexisting SQL queries to PRQL, rather than having to rewrite them manually. For\nmany queries, this should be fairly easy. (For some it will be very difficult,\nbut we can start with the easy ones...)\n\n#### Language\n\nWhile the core semantics and syntax of the language are now fairly stable, we\nare planning\n[a few major features](https://github.com/PRQL/prql/issues?q=is%3Aopen+is%3Aissue+label%3Amajor-feature+label%3Alanguage-design)\nthat will give PRQL the feeling of a real programming language and elevate it in\n[the chomsky hierarchy](https://en.wikipedia.org/wiki/Chomsky_hierarchy).\nHonorable mentions here are recursive CTEs (or rather functions), algebraic type\nsystem, pre-specified join conditions and regex.\n\nNote that these features will probably inflict breaking changes with each minor\nrelease before we stabilize the 1.0, the first indefinitely supported language\nedition.\n\n<--->\n\n#### Alternative backends\n\nCurrently, PRQL only transpiles into SQL, using connectors such as DuckDB to\naccess other formats, such as Pandas dataframes. But PRQL can be much more\ngeneral than SQL — we could directly compile to any relational backend, offering\nmore flexibility and performance — and a consistent experience for those who use\nmultiple tools.\n\nFor example, we could compile PRQL to RQ (Relational Query intermediate\nrepresentation) and then use that to apply the transformations to an in-memory\ndataframe of a performance-optimized library (such as\n[Polars](https://www.pola.rs/)) or a Google Sheets spreadsheet. Alternatively,\nwe could even convert RQ to [Substrait](https://substrait.io/).\n\n### PRQL IDE\n\nWe'd like to make it easier to try PRQL. We currently have the playground, which\ncompiles PRQL and runs queries with a DuckDB wasm module, but there's much more\nwe could do. Could we support for importing arbitrary CSV and parquet input\nfiles and then exporting the results? Could it integrate an LSP?\n\nWe can balance this against building integrations with existing tools.\n\n{{< /columns >}}\n\n## Not in focus\n\nWe should focus on solving a distinct problem really well. PRQL's goal is to\nmake reading and writing analytical queries easier, and so for the moment that\nmeans putting some things out of scope:\n\n- Building infrastructure outside of queries, like lineage. dbt is excellent at\n  that! ([#13](https://github.com/PRQL/prql/issues/13)).\n- Writing DDL / index / schema manipulation / inserting data\n  ([#16](https://github.com/PRQL/prql/issues/16)).\n"
  },
  {
    "path": "web/website/data/examples/basic.yaml",
    "content": "label: Basic example\nprql: |\n  from employees\n  select {id, first_name, age}\n  sort age\n  take 10\nsql: |\n  SELECT\n    id,\n    first_name,\n    age\n  FROM\n    employees\n  ORDER BY\n    age\n  LIMIT\n    10\n"
  },
  {
    "path": "web/website/data/examples/dialects.yaml",
    "content": "label: Dialects\nprql: |\n  prql target:sql.mssql  # Will generate TOP rather than LIMIT\n\n  from employees\n  sort age\n  take 10\nsql: |\n  SELECT\n    *\n  FROM\n    employees\n  ORDER BY\n    age OFFSET 0 ROWS\n  FETCH\n    FIRST 10 ROWS ONLY\n"
  },
  {
    "path": "web/website/data/examples/expressions.yaml",
    "content": "label: Expressions\nprql: |\n  from track_plays\n  derive {\n    finished = started - unfinished,\n    fin_share = finished / started,        # Use previous definitions\n    fin_ratio = fin_share / (1-fin_share), # BTW, hanging commas are optional!\n  }\n\nsql: |\n  SELECT\n    *,\n    started - unfinished AS finished,\n    (started - unfinished) / started AS fin_share,\n    (started - unfinished) / started / (1 - (started - unfinished) / started)\n     AS fin_ratio\n  FROM\n    track_plays\n"
  },
  {
    "path": "web/website/data/examples/f-strings.yaml",
    "content": "label: F-strings\nprql: |\n  from web\n  # Just like Python\n  select url = f\"https://www.{domain}.{tld}/{page}\"\nsql: |\n  SELECT\n    CONCAT('https://www.', domain, '.', tld, '/', page) AS url\n  FROM\n    web\n"
  },
  {
    "path": "web/website/data/examples/friendly-syntax.yaml",
    "content": "label: Friendly syntax\nprql: |\n  from track_plays\n  filter plays > 10_000                # Readable numbers\n  filter (length | in 60..240)         # Ranges with `..`\n  filter recorded > @2008-01-01        # Simple date literals\n  filter released - recorded < 180days # Nice interval literals\n  sort {-length}                       # Concise order direction\n\nsql: |\n  SELECT\n    *\n  FROM\n    track_plays\n  WHERE\n    plays > 10000\n    AND length BETWEEN 60 AND 240\n    AND recorded > DATE '2008-01-01'\n    AND released - recorded < INTERVAL 180 DAY\n  ORDER BY\n    length DESC\n"
  },
  {
    "path": "web/website/data/examples/functions.yaml",
    "content": "label: Functions\nprql: |\n  let celsius_to_fahrenheit = temp -> temp * 9/5 + 32\n\n  from weather\n  select temp_f = (celsius_to_fahrenheit temp_c)\nsql: |\n  SELECT\n    temp_c * 9 / 5 + 32 AS temp_f\n  FROM\n    weather\n"
  },
  {
    "path": "web/website/data/examples/hero.yaml",
    "content": "prql: |\n  from invoices\n  filter invoice_date >= @1970-01-16\n  derive {\n    transaction_fees = 0.8,\n    income = total - transaction_fees\n  }\n  filter income > 1\n  group customer_id (\n    aggregate {\n      average total,\n      sum_income = sum income,\n      ct = count total,\n    }\n  )\n  sort {-sum_income}\n  take 10\n  join c=customers (==customer_id)\n  derive name = f\"{c.last_name}, {c.first_name}\"\n  select {\n    c.customer_id, name, sum_income\n  }\n  derive db_version = s\"version()\"\n"
  },
  {
    "path": "web/website/data/examples/joins.yaml",
    "content": "label: Joins\nprql: |\n  from employees\n  join b=benefits (==employee_id)\n  join side:left p=positions (p.id==employees.employee_id)\n  select {employees.employee_id, p.role, b.vision_coverage}\nsql: |\n  SELECT\n    employees.employee_id,\n    p.role,\n    b.vision_coverage\n  FROM\n    employees\n    INNER JOIN benefits AS b ON employees.employee_id = b.employee_id\n    LEFT OUTER JOIN positions AS p ON p.id = employees.employee_id\n"
  },
  {
    "path": "web/website/data/examples/null-handling.yaml",
    "content": "label: Null handling\nprql: |\n  from users\n  filter last_login != null\n  filter deleted_at == null\n  derive channel = channel ?? \"unknown\"\nsql: |\n  SELECT\n    *,\n    COALESCE(channel, 'unknown') AS channel\n  FROM\n    users\n  WHERE\n    last_login IS NOT NULL\n    AND deleted_at IS NULL\n"
  },
  {
    "path": "web/website/data/examples/orthogonal.yaml",
    "content": "label: Orthogonality\nprql: |\n  from employees\n  # `filter` before aggregations...\n  filter start_date > @2021-01-01\n  group country (\n    aggregate {max_salary = max salary}\n  )\n  # ...and `filter` after aggregations!\n  filter max_salary > 100_000\nsql: |\n  SELECT\n    country,\n    MAX(salary) AS max_salary\n  FROM\n    employees\n  WHERE\n    start_date > DATE '2021-01-01'\n  GROUP BY\n    country\n  HAVING\n    MAX(salary) > 100000\n"
  },
  {
    "path": "web/website/data/examples/s-strings.yaml",
    "content": "label: S-strings\nprql: |\n  # There's no `version` in PRQL, but s-strings\n  # let us embed SQL as an escape hatch:\n  from x\n  derive db_version = s\"version()\"\nsql: |\n  SELECT\n    *,\n    version() AS db_version\n  FROM x\n"
  },
  {
    "path": "web/website/data/examples/top-n.yaml",
    "content": "label: Top N by group\nprql: |\n  # Most recent employee in each role\n  # Quite difficult in SQL...\n  from employees\n  group role (\n    sort join_date\n    take 1\n  )\nsql: |\n  WITH table_0 AS (\n    SELECT\n      *,\n      ROW_NUMBER() OVER (\n        PARTITION BY role\n        ORDER BY\n          join_date\n      ) AS _expr_0\n    FROM\n      employees\n  )\n  SELECT\n    *\n  FROM\n    table_0\n  WHERE\n    _expr_0 <= 1\n"
  },
  {
    "path": "web/website/data/examples/windows.yaml",
    "content": "label: Windows\nprql: |\n  from employees\n  group employee_id (\n    sort month\n    window rolling:12 (\n      derive {trail_12_m_comp = sum paycheck}\n    )\n  )\nsql: |\n  SELECT\n    *,\n    SUM(paycheck) OVER (\n      PARTITION BY employee_id\n      ORDER BY\n        month ROWS BETWEEN 11 PRECEDING AND CURRENT ROW\n    ) AS trail_12_m_comp\n  FROM\n    employees\n"
  },
  {
    "path": "web/website/data/testimonials.yaml",
    "content": "# Tweets can be fetched with https://tweetic.zernonia.com\n#\n# (though FYI I (@max-sixty) couldn't get this to work and had to fill out\n# manually, using https://commentpicker.com/twitter-id.php to get the ID, and\n# manually inspecting the Twitter profile page to get the `pbs.twimg` link...)\n\n- quote:\n    text:\n      It starts with FROM, it fixes trailing commas, and it's called PRQL?? If\n      this is a dream, don't wake me up.\n    author: Jeremiah Lowin, Founder & CEO, Prefect.\n- tweet:\n    user_id: \"19042640\"\n    name: \"Hamilton Ulmer\"\n    screen_name: \"hamiltonulmer\"\n    profile_image_url_https: \"https://pbs.twimg.com/profile_images/1201721914814656512/B6muDm76_normal.jpg\"\n    url: \"https://twitter.com/hamiltonulmer/status/1522562664467107840\"\n    profile_url: \"https://twitter.com/hamiltonulmer\"\n    created_at: \"2022-05-06T13:03:21.000Z\"\n    favorite_count: 2\n    conversation_count: 0\n    text: very excited for prql!\n\n- tweet:\n    user_id: \"12963432\"\n    name: Armin Ronacher\n    screen_name: mitsuhiko\n    profile_image_url_https: \"https://pbs.twimg.com/profile_images/1433982028/profile_normal.png\"\n    url: https://twitter.com/mitsuhiko/status/1683941196799045632?s=20\n    profile_url: https://twitter.com/mitsuhiko\n    created_at: \"2022-07-25T13:03:21.000Z\"\n    favorite_count: 49\n    conversation_count: 4\n    text:\n      \"Oh wow I missed this. Clickhouse now supports PRQL:\n      https://github.com/ClickHouse/ClickHouse/pull/50686\"\n\n- quote:\n    text:\n      I'm also really excited about efforts to create entire new query languages\n      that compile to SQL, like Malloy and PRQL.\n    author: Wes McKinney, Creator of Pandas\n    link: https://wesmckinney.com/blog/looking-back-15-years/\n\n- tweet:\n    user_id: \"16080017\"\n    name: \"Swanand.\"\n    screen_name: \"_swanand\"\n    profile_image_url_https: \"https://pbs.twimg.com/profile_images/1607146722555224064/iTL4gp7m_normal.jpg\"\n    url: \"https://twitter.com/_swanand/status/1485965394880131081\"\n    profile_url: \"https://twitter.com/_swanand\"\n    created_at: \"2022-01-25T13:18:52.000Z\"\n    favorite_count: 20\n    conversation_count: 2\n    text: >\n      A few years ago, I started working on a language, called \"dsql\", short for\n      declarative SQL, and a pun on \"the sequel (to SQL)\". I kinda chickened out\n      of it then, the amount of study and research I needed was massive. prql\n      here is better than I imagined: github.com/max-sixty/prql\n\n- quote:\n    text:\n      Column aliases would have saved me hundreds of hours over the course of my\n      career.\n    author: \"@dvasdekis\"\n    link: https://news.ycombinator.com/item?id=30064873\n- tweet:\n    user_id: \"231773031\"\n    name: \"Rishabh Software\"\n    screen_name: \"RishabhSoft\"\n    profile_image_url_https: \"https://pbs.twimg.com/profile_images/551974110566178817/JHuUzhjU_normal.png\"\n    url: \"https://twitter.com/RishabhSoft/status/1514280454890872833\"\n    profile_url: \"https://twitter.com/RishabhSoft\"\n    created_at: \"2022-04-13T16:32:49.000Z\"\n    favorite_count: 0\n    conversation_count: 0\n    text: >\n      SQL's hold on data retrieval is slipping! 8 new databases are emerging,\n      and some speak entirely new languages for data querying. Know more\n      infoworld.com/article/365490… #SQL #DataQuery #GraphQL #PRQL #WebAssembly\n\n- tweet:\n    user_id: \"40653789\"\n    name: \"Burak Emir\"\n    screen_name: \"burakemir\"\n    profile_image_url_https: \"https://pbs.twimg.com/profile_images/215834651/BurakEmir-2007-04-23-full_normal.jpg\"\n    url: \"https://twitter.com/burakemir/status/1485958835844100098\"\n    profile_url: \"https://twitter.com/burakemir\"\n    created_at: \"2022-01-25T12:52:48.000Z\"\n    favorite_count: 2\n    conversation_count: 1\n    text: >\n      I want to give the PRQL a little boost here, \"pipeline of transformations\"\n      is IMHO a good choice for readable query languages that need to deal with\n      SQL-like aggregations, group by and count and sum all:\n      github.com/max-sixty/prql\n- quote:\n    text: >\n      Having written some complex dbt projects...the first thing...it gets right\n      is to start with the table and work down. This is an enormous readability\n      boost in large projects and leads to great intellisense.\n    author: Ruben Slabbert\n    link: https://lobste.rs/s/oavgcx/prql_simpler_more_powerful_sql#c_nmzcd7\n"
  },
  {
    "path": "web/website/static/CNAME",
    "content": "prql-lang.org\n"
  },
  {
    "path": "web/website/themes/prql-theme/archetypes/default.md",
    "content": "---\ntitle: \"{{ replace .Name \"-\" \" \" | title }}\"\ndate: {{ .Date }}\ndraft: true\n---\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/404.html",
    "content": "{{ define \"main\" }}\n  <main id=\"main\" class=\"container py-5\">\n    <h1>Not found</h1>\n\n    <p>This page does not exist.</p>\n  </main>\n{{ end }}\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/_default/_markup/render-link.html",
    "content": "{{/*\n\nFormats external links with an icon, and opens in a new tab.\n\nNote that it's important not to have a blank line at the end; so we exclude this\nfile from prettier / pre-commit's fixer\n\n  */}}\n{{ $is_external := strings.HasPrefix .Destination \"http\" }}\n<a\n  href=\"{{ .Destination | safeURL }}\"\n  {{ with .Title }}\n    title=\"{{ . }}\"\n  {{ end }}\n  {{ if $is_external }}\n    target=\"_blank\" rel=\"noopener\"\n  {{ end }}\n>\n  {{ .Text | safeHTML }}\n  {{- if $is_external -}}\n    <sup><i class=\"bx bx-link-external\"></i></sup>\n  {{- end -}}\n</a>"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/_default/article.html",
    "content": "{{ define \"main\" }}\n  <main id=\"main\" class=\"container\">\n    <div class=\"row\">\n      <article class=\"col-12 col-lg-6 offset-lg-3\">\n        {{ if not .Params.no_head }}\n          <h1>{{ .Title }}</h1>\n          <div class=\"d-flex gap-3 text-muted flex-wrap\">\n            {{ range .Params.Authors }}\n              <span>{{ . }}</span>&middot;\n            {{ end }}\n            {{ if .Date }}\n              <time datetime=\"{{ .Date.Format \" 2006-01-02T15:04:05Z07:00\" }}\"\n                >{{ .Date.Format \"January 02, 2006\" }}</time\n              >\n            {{ end }}\n            {{ if (lt .Date .Lastmod) }}\n              &middot;\n              <div>\n                Updated\n                <time\n                  datetime=\"{{ .Lastmod.Format \" 2006-01-02T15:04:05Z07:00\" }}\"\n                  >{{ .Lastmod.Format \"January 02, 2006\" }}</time\n                >\n              </div>\n            {{ end }}\n          </div>\n        {{ end }}\n        <hr class=\"mt-1\" />\n\n        {{ .Content }}\n      </article>\n      {{ if .Params.toc }}\n        <aside class=\"col-3 d-none d-lg-block toc\">\n          {{ .TableOfContents }}\n        </aside>\n      {{ end }}\n    </div>\n  </main>\n{{ end }}\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/_default/baseof.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  {{ partial \"head.html\" . }}\n\n\n  <body class=\"d-flex flex-column vh-100 overflow-hidden\">\n    {{ partial \"header.html\" . }}\n\n\n    <div class=\"d-flex flex-column flex-grow-1 overflow-scroll\">\n      {{- block \"main\" . }}{{- end }}\n\n      {{- block \"footer\" . }}\n        {{ partial \"footer.html\" . }}\n      {{- end }}\n    </div>\n\n    <a\n      href=\"#\"\n      class=\"back-to-top d-flex align-items-center justify-content-center\"\n      ><i class=\"bx bx-up-arrow-alt\"></i\n    ></a>\n\n    <script src=\"{{ \"/main.js\" | relURL }}\"></script>\n    <script src=\"{{ \"/plugins/bootstrap/bootstrap.bundle.min.js\" | relURL }}\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/_default/big_iframe.html",
    "content": "{{ define \"footer\" }}\n  <!--no footer here-->\n{{ end }}\n{{ define \"main\" }}\n  <main id=\"main\" class=\"big-iframe\">\n    {{ .Content }}\n  </main>\n{{ end }}\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/_default/home.html",
    "content": "{{ define \"main\" }}\n  <!-- ======= Hero Section ======= -->\n  {{ with .Params.hero_section }}\n    {{ if .enable }}\n      <section class=\"hero striped\">\n        <div class=\"container\">\n          <div class=\"row content align-items-center\">\n            <div class=\"col-lg-6\">\n              <h4 class=\"mb-3\">\n                <span>P</span>ipelined <span>R</span>elational\n                <span>Q</span>uery <span>L</span>anguage, pronounced\n                <span>“Prequel”</span>\n              </h4>\n              <h2 class=\"mb-3\">{{ .heading | markdownify }}</h2>\n              <p class=\"mb-3 bottom-text\">{{ .bottom_text | markdownify }}</p>\n              {{ if .button.enable }}\n                {{ with .button }}\n                  <a\n                    class=\"btn\"\n                    href=\"{{ .link | safeURL }}\"\n                    title=\"{{ .label }}\"\n                  >\n                    {{ .label }}</a\n                  >\n                {{ end }}\n              {{ end }}\n            </div>\n            <div class=\"col-lg-6 pt-4 pt-lg-0\">\n              <pre tabindex=\"0\">\n          <code class=\"language-prql hljs\" data-lang=\"prql\">{{ $.Site.Data.examples.hero.prql }}</code>\n        </pre>\n            </div>\n          </div>\n        </div>\n      </section>\n    {{ end }}\n  {{ end }}\n  <!-- End Hero -->\n\n  <main id=\"main\">\n    <!-- ======= Why PRQL Section ======= -->\n    {{ with .Params.why_prql_section }}\n      {{ if .enable }}\n        <section class=\"big-cards-section\">\n          <div class=\"container\">\n            <div class=\"row\">\n              <div class=\"section-title\">\n                <h2>{{ .title | markdownify }}</h2>\n              </div>\n            </div>\n            <div class=\"row row-cols-1 row-cols-lg-2 justify-content-center\">\n              {{ range .items }}\n                <div class=\"col g-3\">\n                  <div class=\"card\">\n                    <h4>{{ .title | markdownify }}</h4>\n                    {{/* Probably there's a more general way of formatting\n                      this padding; the default is 2em, which looked too indented\n                    */}}\n                    <ul style=\"padding-left:1em\">\n                      {{ range .content }}\n                        <li>{{ . | markdownify }}</li>\n                      {{ end }}\n                    </ul>\n                  </div>\n                </div>\n              {{ end }}\n            </div>\n          </div>\n        </section>\n      {{ end }}\n    {{ end }}\n\n    {{ with .Params.showcase_section }}\n      {{ if .enable }}\n        <section class=\"content-section showcase-section section-bg\">\n          <div class=\"container\">\n            <div class=\"row content\">\n              <div class=\"col-lg-4 mb-4\">\n                <div class=\"section-title\">\n                  <h2 class=\"mb-3\">{{ .title | markdownify }}</h2>\n                </div>\n\n                {{ range .content }}\n                  <p class=\"mb-3\">{{ . }}</p>\n                {{ end }}\n                {{ range .buttons }}\n                  <a class=\"btn\" href=\"{{ .link | relURL }}\">{{ .label }}</a>\n                {{ end }}\n              </div>\n\n              <div class=\"col-lg-8 d-flex align-items-start\">\n                <div\n                  class=\"nav flex-column nav-pills flex-shrink-0 me-3\"\n                  id=\"v-pills-tab\"\n                  role=\"tablist\"\n                  aria-orientation=\"vertical\"\n                >\n                  {{ range $index, $e := .examples }}\n                    <button\n                      class=\"nav-link {{ if (eq 0 $index) }}active{{ end }}\"\n                      id=\"v-pills-{{ $e }}-tab\"\n                      data-bs-toggle=\"pill\"\n                      data-bs-target=\"#v-pills-{{ $e }}\"\n                      type=\"button\"\n                      role=\"tab\"\n                      aria-controls=\"v-pills-{{ $e }}\"\n                      aria-selected=\"false\"\n                    >\n                      {{ $example := or (index site.Data.examples $e) (errorf \"couldn't find example %q (expected such a .yaml file in data/examples)\" $e) }}\n                      {{ $example.label }}\n                    </button>\n                  {{ end }}\n                </div>\n                <div class=\"tab-content\" id=\"v-pills-tabContent\">\n                  {{ range $index, $e := .examples }}\n                    <div\n                      class=\"tab-pane fade{{ if (eq 0 $index) }}\n                        show active\n                      {{ end }} row\"\n                      id=\"v-pills-{{ $e }}\"\n                      role=\"tabpanel\"\n                      aria-labelledby=\"v-pills-{{ $e }}-tab\"\n                      tabindex=\"0\"\n                    >\n                      {{ $example := index $.Site.Data.examples $e }}\n                      <pre><code class=\"language-prql hljs\" data-lang=\"prql\">{{ $example.prql }}</code></pre>\n                      <pre><code class=\"language-sql hljs\" data-lang=\"sql\">{{ $example.sql }}</code></pre>\n                    </div>\n                  {{ end }}\n                </div>\n              </div>\n            </div>\n          </div>\n        </section>\n      {{ end }}\n    {{ end }}\n\n    {{ with .Params.principles_section }}\n      {{/* TODO: can we integrate this with section-cards? It's very similar */}}\n      {{ if .enable }}\n        <section class=\"big-cards-section\">\n          <div class=\"container\">\n            <div class=\"row\">\n              <div class=\"section-title\">\n                <h2>{{ .title | markdownify }}</h2>\n              </div>\n            </div>\n            <div class=\"row row-cols-1 row-cols-lg-3 justify-content-center\">\n              {{ range .items }}\n                <div class=\"col g-3\">\n                  <div class=\"card\">\n                    <h4>{{ .title | markdownify }}</h4>\n                    <p class=\"blue-border\">\n                      <strong>{{ .main_text | markdownify }}</strong>\n                    </p>\n                    <p>{{ .content | markdownify }}</p>\n                  </div>\n                </div>\n              {{ end }}\n            </div>\n          </div>\n        </section>\n      {{ end }}\n    {{ end }}\n\n    {{ with .Params.videos_section }}\n      {{ if .enable }}\n        <section class=\"videos-section\">\n          <div class=\"container\">\n            <div class=\"row\">\n              <div class=\"section-title\">\n                <h2>{{ .title | markdownify }}</h2>\n              </div>\n            </div>\n            <div class=\"row row-cols-1 row-cols-lg-3\">\n              {{ range .items }}\n                <div class=\"col g-3\">\n                  <iframe\n                    width=\"560\"\n                    height=\"315\"\n                    src=\"https://www.youtube-nocookie.com/embed/{{ .youtube_id }}\"\n                    title=\"YouTube video player\"\n                    frameborder=\"0\"\n                    allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n                    allowfullscreen\n                  ></iframe>\n                </div>\n              {{ end }}\n            </div>\n          </div>\n        </section>\n      {{ end }}\n    {{ end }}\n\n    {{ partial \"section-cards\" .Params.integrations_section }}\n    {{ partial \"section-cards\" .Params.tools_section }}\n    {{ partial \"section-cards\" .Params.bindings_section }}\n    {{ partial \"section-testimonials\" .Params.testimonials_section }}\n  </main>\n{{ end }}\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/_default/list.html",
    "content": "{{ define \"main\" }}\n  <main id=\"main\" class=\"container list py-5\">\n    <h1>{{ .Title }}</h1>\n\n    {{ if .Content }}\n      <div class=\"content\">{{ .Content }}</div>\n    {{ end }}\n\n    {{ range .Pages }}\n      <h3 class=\"post-item row\">\n        <div class=\"col-12 col-xxl-3 text-muted text-end\">\n          <time datetime=\"{{ .Date.Format \" 2006-01-02T15:04:05Z07:00\" }}\"\n            >{{ .Date.Format \"January 02, 2006\" }}</time\n          >\n        </div>\n        <a class=\"col\" href=\"{{ .Permalink }}\">{{ .Title }}</a>\n      </h3>\n    {{ end }}\n\n  </main>\n{{ end }}\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/_default/single.html",
    "content": "{{ define \"main\" }}\n  <main id=\"main\" class=\"container\">\n    <article>\n      {{ if not .Params.no_head }}\n        <h1>{{ .Title }}</h1>\n        {{ if .Date }}\n          <time datetime=\"{{ .Date.Format \"2006-01-02T15:04:05Z07:00\" }}\"\n            >{{ .Date.Format \"January 02, 2006\" }}</time\n          >\n        {{ end }}\n      {{ end }}\n\n      {{ .Content }}\n    </article>\n  </main>\n{{ end }}\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/partials/footer.html",
    "content": "<footer id=\"footer\" class=\"mt-auto\">\n  <div class=\"container\">\n    <div class=\"row py-3\">\n      <h2>Join us</h2>\n      <div class=\"col-md-8 py-1\">\n        {{ with site.Params.contribute }}\n\n          <p>{{ .subtitle | markdownify }}</p>\n\n          <ul>\n            {{ range .list }}\n              <li>{{ .text | markdownify }}</li>\n            {{ end }}\n          </ul>\n\n          {{ if .button }}\n            <a\n              class=\"btn\"\n              href=\"{{ .button.link }}\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              >{{ .button.text }}<sup><i class=\"bx bx-link-external\"></i></sup\n            ></a>\n          {{ end }}\n\n        {{ end }}\n      </div>\n      <div class=\"col-md-4 py-1\">\n        {{ with site.Params.follow }}\n          <p>{{ .subtitle | markdownify }}</p>\n          <ul>\n            {{ range .list }}\n              <li>{{ .text | markdownify }}</li>\n            {{ end }}\n          </ul>\n        {{ end }}\n\n        {{/* TODO: put the content of these buttons in the same place as the links */}}\n\n\n        <p>\n          <a\n            class=\"btn-icon\"\n            href=\"https://github.com/PRQL/prql\"\n            target=\"_blank\"\n            title=\"GitHub repository\"\n            ><i class=\"bx bxl-github\"></i\n          ></a>\n\n          <a\n            class=\"btn-icon\"\n            href=\"https://discord.com/invite/eQcfaCmsNc\"\n            target=\"_blank\"\n            title=\"Join the Discord\"\n            ><i class=\"bx bxl-discord\"></i\n          ></a>\n\n          <a\n            class=\"btn-icon\"\n            href=\"https://twitter.com/prql_lang\"\n            target=\"_blank\"\n            title=\"Follow us on Twitter\"\n            ><i class=\"bx bxl-twitter\"></i\n          ></a>\n        </p>\n      </div>\n    </div>\n    <p><small>{{ site.Params.license }}</small></p>\n  </div>\n</footer>\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/partials/head.html",
    "content": "<head>\n  <meta charset=\"utf-8\" />\n  <meta content=\"width=device-width, initial-scale=1.0\" name=\"viewport\" />\n  {{ template \"_internal/opengraph.html\" . }}\n\n\n  <title>{{ .Page.Title }}</title>\n  <meta content=\"{{ .Page.Description }}\" name=\"description\" />\n  <meta content=\"{{ .Page.Keywords }}\" name=\"keywords\" />\n\n  <!-- Favicons -->\n  <link\n    rel=\"apple-touch-icon\"\n    sizes=\"180x180\"\n    href=\"{{ \"/img/apple-touch-icon.png\" | relURL }}\"\n  />\n  <!--32*32-->\n  <link\n    rel=\"icon\"\n    type=\"image/png\"\n    sizes=\"32x32\"\n    href=\"{{ \"/img/favicon-32x32.png\" | relURL }}\"\n  />\n  <!--16*16-->\n  <link\n    rel=\"icon\"\n    type=\"image/png\"\n    sizes=\"16x16\"\n    href=\"{{ \"/img/favicon-16x16.png\" | relURL }}\"\n  />\n\n  <!-- Vendor CSS Files -->\n  <link\n    href=\"{{ \"/plugins/bootstrap/bootstrap.min.css\" | relURL }}\"\n    rel=\"stylesheet\"\n  />\n  <link href=\"{{ \"/fonts/boxicons.min.css\" | relURL }}\" rel=\"stylesheet\" />\n\n  <!--Highlights-->\n  <link href=\"/plugins/highlight/highlight.css\" rel=\"stylesheet\" />\n  <script defer=\"defer\" src=\"/plugins/highlight/highlight.min.js\"></script>\n  <script defer=\"defer\" src=\"/plugins/highlight/prql.js\"></script>\n\n  <!-- CSS File -->\n  <link href=\"{{ \"/style.css\" | relURL }}\" rel=\"stylesheet\" />\n\n  <!-- From https://buttons.github.io/ -->\n  <script async defer src=\"https://buttons.github.io/buttons.js\"></script>\n\n  {{ template \"_internal/google_analytics.html\" . }}\n\n</head>\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/partials/header.html",
    "content": "<!-- Place this tag in your head or just before your close body tag. -->\n<header id=\"header\" class=\"flex-shrink-0 d-flex align-items-center\">\n  <div class=\"container d-flex align-items-center\">\n    <a href=\"/\" class=\"logo me-auto d-flex align-items-end\">\n      <img\n        src=\"/img/icon.svg\"\n        alt=\"\"\n        class=\"img-fluid\"\n        height=\"50\"\n        width=\"50\"\n      />\n      <h1>PRQL</h1>\n    </a>\n\n    <nav id=\"navbar\" class=\"navbar navbar-expand-lg\">\n      <button class=\"navbar-toggler border-0 bg-white p-0\" type=\"button\" data-bs-toggle=\"offcanvas\" data-bs-target=\"#offcanvasNavbar\" aria-controls=\"offcanvasNavbar\" aria-label=\"Toggle navigation\">\n        <i class=\"bx bx-dots-horizontal-rounded fs-2\"></i>\n      </button>\n      <div class=\"offcanvas offcanvas-end\" tabindex=\"-1\" id=\"offcanvasNavbar\" aria-labelledby=\"offcanvasNavbarLabel\">\n        <div class=\"offcanvas-header\">\n          <h5 class=\"offcanvas-title\" id=\"offcanvasNavbarLabel\">PRQL</h5>\n          <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"offcanvas\" aria-label=\"Close\"></button>\n        </div>\n        <div class=\"offcanvas-body\">\n          <hr class=\"d-lg-none text-black-50\">\n          <ul class=\"navbar-nav justify-content-end flex-grow-1 pe-3\">\n            {{ range .Site.Menus.nav }}\n              <li class=\"nav-item col-6 col-lg-auto py-2 px-0 px-lg-2\">\n                {{ print \"[\" .Name \"]\" \"(\"  .URL \")\" | markdownify }}\n              </li>\n            {{ end }}\n          </ul>\n        </div>\n      </div>\n    </nav>\n\n    <div\n      style=\"min-width: 9rem\"\n      class=\"d-flex ps-3 pt-2 justify-content-end align-items-end\"\n    >\n      <!-- Generated from https://buttons.github.io/ -->\n      <a\n        class=\"github-button\"\n        href=\"https://github.com/PRQL/prql\"\n        data-size=\"large\"\n        data-show-count=\"true\"\n        aria-label=\"Star prql/prql on GitHub\"\n      >\n        Star\n      </a>\n    </div>\n  </div>\n</header>\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/partials/section-cards.html",
    "content": "{{ if .enable }}\n  <section\n    class=\"content-section cards-section\"\n    {{ if .section_id }}id=\"{{ .section_id }}\"{{ end }}\n  >\n    <div class=\"container\">\n      <div class=\"row content\">\n        <div class=\"section-title\">\n          <h2>{{ .title | markdownify }}</h2>\n        </div>\n      </div>\n      <div class=\"row row-cols-1 row-cols-lg-4 justify-content-left\">\n        {{ range .sections }}\n          <div class=\"col g-3\">\n            <div class=\"card\">\n              {{ if and .link .label }}\n                <h4>\n                  {{/* We recreate the markdown so external links will parse &\n                    display as such.\n                  */}}\n                  {{ print \"[\" .label \"]\" \"(\" .link \")\" | markdownify }}\n                </h4>\n              {{ else }}\n                <h4 class=\"text-muted\">{{ .label }}</h4>\n              {{ end }}\n              {{ .text | markdownify }}\n            </div>\n          </div>\n        {{ end }}\n      </div>\n    </div>\n  </section>\n{{ end }}\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/partials/section-testimonials.html",
    "content": "{{ if .enable }}\n  <section class=\"content-section testimonials-section\">\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"section-title\">\n          <h2>{{ .title | markdownify }}</h2>\n        </div>\n      </div>\n      <div class=\"row row-cols-1 row-cols-md-3 g-4\">\n        {{ range site.Data.testimonials }}\n          <div class=\"col\">\n            {{/* Margin-top & margin-bottom are set to match Tweets. Maybe there's\n              a better way of designing this? (and maybe we design from the stylesheet?)\n            */}}\n            {{ with .quote }}\n              <div class=\"card quote\">\n                {{ if .link }}\n                  <a\n                    class=\"quote-text\"\n                    href=\"{{ .link }}\"\n                    rel=\"noopener\"\n                    target=\"_blank\"\n                  >\n                    {{ .text }}\n                    <span> — {{ .author }} </span>\n                  </a>\n                {{ else }}\n                  <p class=\"quote-text\">\n                    {{ .text }}\n                    <span> — {{ .author }} </span>\n                  </p>\n                {{ end }}\n              </div>\n            {{ end }}\n            {{ with .tweet }}\n\n              <div class=\"card tweet\">\n                <div class=\"tweet-header\">\n                  <div class=\"tweet-author\">\n                    <svg\n                      class=\"tweet-logo\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                      xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n                      aria-hidden=\"true\"\n                      role=\"img\"\n                      width=\"32\"\n                      height=\"32\"\n                      preserveAspectRatio=\"xMidYMid meet\"\n                      viewBox=\"0 0 24 24\"\n                    >\n                      <path\n                        fill=\"currentColor\"\n                        d=\"M22.46 6c-.77.35-1.6.58-2.46.69c.88-.53 1.56-1.37 1.88-2.38c-.83.5-1.75.85-2.72 1.05C18.37 4.5 17.26 4 16 4c-2.35 0-4.27 1.92-4.27 4.29c0 .34.04.67.11.98C8.28 9.09 5.11 7.38 3 4.79c-.37.63-.58 1.37-.58 2.15c0 1.49.75 2.81 1.91 3.56c-.71 0-1.37-.2-1.95-.5v.03c0 2.08 1.48 3.82 3.44 4.21a4.22 4.22 0 0 1-1.93.07a4.28 4.28 0 0 0 4 2.98a8.521 8.521 0 0 1-5.33 1.84c-.34 0-.68-.02-1.02-.06C3.44 20.29 5.7 21 8.12 21C16 21 20.33 14.46 20.33 8.79c0-.19 0-.37-.01-.56c.84-.6 1.56-1.36 2.14-2.23Z\"\n                      ></path>\n                    </svg>\n                    {{/* The Firefox tracking protection blocks images hosted on pbs.twitter.com, see https://bugzilla.mozilla.org/show_bug.cgi?id=1458915\n                      So we just download the images when building the website.\n                    */}}\n                    {{ $imageSrc := (resources.GetRemote \"https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png\" | resources.Copy \"default.png\").RelPermalink }}\n                    {{ $localImgPath := printf \"cached-avatars/%s.png\" .screen_name }}\n                    {{ with resources.GetRemote .profile_image_url_https }}\n                      {{ $imageSrc = (resources.Copy $localImgPath .).RelPermalink }}\n                    {{ else }}\n                      {{ warnf \"[section-testimonials.html] couldn't fetch remote image %v\" .profile_image_url_https }}\n                    {{ end }}\n                    <img class=\"tweet-author-image\" src=\"{{ $imageSrc }}\" />\n                    <div class=\"tweet-author-info\">\n                      <p class=\"tweet-author-name\">{{ .name }}</p>\n                      <a\n                        class=\"tweet-author-handler\"\n                        target=\"_blank\"\n                        href=\"{{ .profile_url }}\"\n                        >@{{ .screen_name }}</a\n                      >\n                    </div>\n                  </div>\n                </div>\n\n                <div class=\"mt-2\">\n                  {{ .text }}\n                </div>\n\n                <span class=\"flex-grow-1\"></span>\n\n                <div class=\"tweet-info d-flex pt-2 text-muted\">\n                  <svg\n                    class=\"tweet-info-favourite\"\n                    width=\"24\"\n                    height=\"24\"\n                    viewBox=\"0 0 24 24\"\n                  >\n                    <path\n                      class=\"fill-current\"\n                      d=\"M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.813-1.148 2.353-2.73 4.644-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.375-7.454 13.11-10.037 13.156H12zM7.354 4.225c-2.08 0-3.903 1.988-3.903 4.255 0 5.74 7.035 11.596 8.55 11.658 1.52-.062 8.55-5.917 8.55-11.658 0-2.267-1.822-4.255-3.902-4.255-2.528 0-3.94 2.936-3.952 2.965-.23.562-1.156.562-1.387 0-.015-.03-1.426-2.965-3.955-2.965z\"\n                    ></path>\n                  </svg>\n                  <span class=\"ms-1\">{{ .favorite_count }}</span>\n                  <div class=\"ms-3\">\n                    {{ .created_at | time.Format \"15:04 · Jan 2, 2006\" }}\n                  </div>\n                  <span class=\"flex-grow-1\"></span>\n                  <a href=\"{{ .url }}\">Twitter</a>\n                </div>\n              </div>\n            {{ end }}\n          </div>\n        {{ end }}\n      </div>\n    </div>\n  </section>\n{{ end }}\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/shortcodes/cite.html",
    "content": "<cite>{{ .Inner }}</cite>\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/shortcodes/columns.html",
    "content": "<div class=\"row\">\n  {{ range split .Inner \"<--->\" }}\n    <div class=\"col-md\">{{ . | $.Page.RenderString }}</div>\n  {{ end }}\n</div>\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/shortcodes/faq.html",
    "content": "<details class=\"faq\">\n  <summary><h2>{{ (.Get 0) | markdownify }}</h2></summary>\n  {{ .Inner | markdownify }}\n</details>\n"
  },
  {
    "path": "web/website/themes/prql-theme/layouts/shortcodes/link.html",
    "content": "{{/* Compile a link as markdown so it runs the `render-link` template */}}\n{{ print \"[\" (.Get \"label\") \"]\" \"(\" .Get \"link\" \")\" | markdownify }}\n"
  },
  {
    "path": "web/website/themes/prql-theme/static/main.js",
    "content": "(function () {\n  \"use strict\";\n\n  /**\n   * Easy selector helper function\n   */\n  const select = (el, all = false) => {\n    el = el.trim();\n    if (all) {\n      return [...document.querySelectorAll(el)];\n    } else {\n      return document.querySelector(el);\n    }\n  };\n\n  /**\n   * Easy event listener function\n   */\n  const on = (type, el, listener, all = false) => {\n    let selectEl = select(el, all);\n    if (selectEl) {\n      if (all) {\n        selectEl.forEach((e) => e.addEventListener(type, listener));\n      } else {\n        selectEl.addEventListener(type, listener);\n      }\n    }\n  };\n\n  /**\n   * Easy on scroll event listener\n   */\n  const onscroll = (el, listener) => {\n    el.addEventListener(\"scroll\", listener);\n  };\n\n  /**\n   * Back to top button\n   */\n  let backtotop = select(\".back-to-top\");\n  if (backtotop) {\n    const toggleBacktotop = () => {\n      if (window.scrollY > 100) {\n        backtotop.classList.add(\"active\");\n      } else {\n        backtotop.classList.remove(\"active\");\n      }\n    };\n    window.addEventListener(\"load\", toggleBacktotop);\n    onscroll(document, toggleBacktotop);\n  }\n})();\n"
  },
  {
    "path": "web/website/themes/prql-theme/static/plugins/highlight/highlight.css",
    "content": "/* Highlight.js obsidian theme */\npre code.hljs {\n  display: block;\n  overflow-x: auto;\n}\ncode.hljs {\n  padding: 3px 5px;\n}\n.hljs {\n  color: #e0e2e4;\n}\n.hljs-keyword,\n.hljs-literal,\n.hljs-selector-id,\n.hljs-selector-tag {\n  color: #e4a24b;\n}\n.hljs-number {\n  color: #71b0eb;\n}\n.hljs-params {\n  color: #5b8fc5;\n}\n.hljs-link,\n.hljs-regexp {\n  color: #d39745;\n}\n.hljs-meta {\n  color: #557182;\n}\n.hljs-addition,\n.hljs-built_in,\n.hljs-bullet,\n.hljs-emphasis,\n.hljs-module,\n.hljs-name,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-subst,\n.hljs-tag,\n.hljs-template-tag,\n.hljs-template-variable,\n.hljs-type,\n.hljs-variable {\n  color: #8cbbad;\n}\n.hljs-string,\n.hljs-symbol {\n  color: #d84444;\n}\n.hljs-comment,\n.hljs-deletion,\n.hljs-quote {\n  color: #818e96;\n}\n.hljs-selector-class {\n  color: #a082bd;\n}\n.hljs-doctag,\n.hljs-keyword,\n.hljs-literal,\n.hljs-name,\n.hljs-section,\n.hljs-selector-tag,\n.hljs-strong,\n.hljs-title,\n.hljs-type {\n  font-weight: 700;\n}\n.hljs-class .hljs-title,\n.hljs-code,\n.hljs-section,\n.hljs-title.class_ {\n  color: #fff;\n}\n"
  },
  {
    "path": "web/website/themes/prql-theme/static/plugins/highlight/prql.js",
    "content": "/*\nLanguage: PRQL\nDescription: PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement.\nCategory: common, database\nRequires: markdown.js\nWebsite: https://prql-lang.org/\n*/\n\n// !!keep consistent with\n// https://github.com/PRQL/prql/blob/main/reference/highlight-prql.js\n//\n// TODO: can we import one from the other at build time?\n\n// We should probably grab more from other languages at\n// https://github.com/highlightjs/highlightjs-tsql/tree/main/src/languages.\n// Possibly we can even import parts at runtime, simplifying this file?\n\nformatting = function (hljs) {\n  const BUILTIN_FUNCTIONS = [\n    // Aggregate functions\n    \"any\",\n    \"average\",\n    \"concat_array\",\n    \"count\",\n    \"every\",\n    \"max\",\n    \"min\",\n    \"stddev\",\n    \"sum\",\n    // File reading functions\n    \"read_csv\",\n    \"read_json\",\n    \"read_parquet\",\n    // List functions\n    \"all\",\n    \"map\",\n    \"zip\",\n    \"_eq\",\n    \"_is_null\",\n    // Misc functions\n    \"from_text\",\n    // Window functions\n    \"lag\",\n    \"lead\",\n    \"first\",\n    \"last\",\n    \"rank\",\n    \"rank_dense\",\n    \"row_number\",\n  ];\n\n  const MODULES = [\"date\", \"math\", \"text\"];\n\n  const DATATYPES = [\n    \"bool\",\n    \"float\",\n    \"int\",\n    \"int8\",\n    \"int16\",\n    \"int32\",\n    \"int64\",\n    \"text\",\n    \"timestamp\",\n  ];\n\n  const TRANSFORMS = [\n    \"aggregate\",\n    \"append\",\n    \"derive\",\n    \"filter\",\n    \"from\",\n    \"group\",\n    \"join\",\n    \"select\",\n    \"sort\",\n    \"take\",\n    \"union\",\n    \"window\",\n  ];\n\n  const KEYWORDS = [\"let\", \"prql\", \"into\", \"case\", \"in\", \"as\", \"module\"];\n\n  const CHAR_ESCAPE = {\n    scope: \"char.escape\",\n    match: /\\\\\\\\|\\\\([bfnrt]|u{[0-9A-Fa-f]{1,6}}|x[0-9A-Fa-f]{2})/,\n  };\n\n  return {\n    name: \"PRQL\",\n    case_insensitive: true,\n    keywords: {\n      built_in: BUILTIN_FUNCTIONS,\n      module: MODULES,\n      keyword: [...TRANSFORMS, ...BUILTIN_FUNCTIONS, ...KEYWORDS],\n      literal: \"false true null\",\n      type: DATATYPES,\n    },\n    contains: [\n      {\n        // docblock\n        begin: \"#!\",\n        end: \"$\",\n        subLanguage: \"markdown\",\n        relevance: 0,\n      },\n      hljs.COMMENT(\"#\", \"$\"),\n      {\n        // named arg\n        scope: \"params\",\n        begin: /\\w+\\s*:/,\n        end: \"\",\n        relevance: 10,\n      },\n      {\n        // meta prql for target and version\n        scope: \"meta\",\n        match: /^prql/,\n      },\n      // This seems much too strong at the moment, so disabling. I think ideally\n      // we'd have it for aliases but not assigns.\n      // {\n      //   // assign\n      //   scope: { 1: \"variable\" },\n      //   match: [/\\w+\\s*/, /=[^=]/],\n      //   relevance: 10,\n      // },\n      {\n        // date\n        scope: \"string\",\n        match: /@(\\d*|-|\\.\\d|:|T)+Z?/,\n        relevance: 10,\n      },\n      {\n        // interval\n        scope: \"string\",\n        // Add more as needed\n        match:\n          /\\d+(years|months|weeks|days|hours|minutes|seconds|milliseconds|microseconds)/,\n        relevance: 10,\n      },\n      {\n        scope: \"string\",\n        relevance: 10,\n        variants: [\n          {\n            begin: 'r\"\"\"',\n            end: '\"\"\"',\n          },\n          {\n            begin: 'r\"',\n            end: '\"',\n          },\n        ],\n      },\n      {\n        // interpolation strings: s-strings are variables and f-strings are\n        // strings? (Though possibly that's too cute, open to adjusting)\n        //\n        scope: \"variable\",\n        relevance: 10,\n        variants: [\n          {\n            begin: 's\"\"\"',\n            end: '\"\"\"',\n          },\n          {\n            begin: 's\"',\n            end: '\"',\n          },\n        ],\n        contains: [\n          // I tried having the `f` / `s` be marked differently, but I don't\n          // think it's possible to have a subscope within the begin / end.\n          {\n            // I think `variable` is the right scope rather than defaulting to\n            // white, but not 100% sure; using `subst` is suggested in the docs.\n            scope: \"variable\",\n            begin: /\\{/,\n            end: /\\}/,\n          },\n        ],\n      },\n      {\n        scope: \"string\",\n        relevance: 10,\n        variants: [\n          {\n            begin: 'f\"\"\"',\n            end: '\"\"\"',\n          },\n          {\n            begin: 'f\"',\n            end: '\"',\n          },\n        ],\n        contains: [\n          CHAR_ESCAPE,\n          {\n            scope: \"variable\",\n            begin: \"f\",\n            end: '\"',\n            // excludesEnd: true,\n          },\n          // TODO: would be nice to have this be a different color, but I don't\n          // think it's possible to have a subscope within the begin / end.\n          // {\n          //   scope: \"punctuation\",\n          //   match: /{|}/,\n          // },\n          {\n            scope: \"variable\",\n            begin: /\\{/,\n            end: /\\}/,\n          },\n        ],\n      },\n      {\n        // normal string\n        scope: \"string\",\n        relevance: 10,\n        variants: [\n          // TODO: is there a way of encoding the actual rule here? Otherwise\n          // we're just adding the variants we use...\n          {\n            begin: '\"\"\"\"\"',\n            end: '\"\"\"\"\"',\n          },\n          {\n            begin: '\"\"\"',\n            end: '\"\"\"',\n          },\n          {\n            begin: '\"',\n            end: '\"',\n          },\n          {\n            begin: \"'\",\n            end: \"'\",\n          },\n        ],\n        contains: [CHAR_ESCAPE],\n      },\n      { scope: \"punctuation\", match: /[\\[\\]{}(),]/ },\n      {\n        scope: \"operator\",\n        match: /==|~=|\\+|\\-|\\/|\\*|!=|->|=>|<=|>=|&&|\\|\\||<|>/,\n        relevance: 10,\n      },\n      {\n        scope: \"number\",\n        // Regex explanation:\n        // 1. `\\b`: asserts a word boundary. This ensures that the pattern matches numbers that are distinct words or at the boundaries of words.\n        // 2. `(\\d[_\\d]*(e|E)\\d[_\\d]*)`: This is the first alternative in the main group and matches numbers in scientific notation:\n        //     - `\\d`: matches a digit (0-9).\n        //     - `[_\\d]*`: matches zero or more underscores or digits, representing the numbers before the `e` in scientific notation.\n        //     - `(e|E)`: matches the letter 'e' or 'E' for scientific notation.\n        //     - `\\d`: matches a digit (0-9), the beginning of the exponent.\n        //     - `[_\\d]*`: matches zero or more underscores or digits, representing the numbers after the `e` in scientific notation.\n        // 3. `(\\d[_\\d]*|(\\d\\.[\\d_]*\\d))`: This is the second alternative in the main group and matches standard numbers without the scientific notation:\n        //     - `\\d[_\\d]*`: matches a sequence starting with a digit and followed by zero or more digits or underscores.\n        //     - `|`: OR\n        //     - `(\\d\\.[\\d_]*\\d)`: matches numbers with a decimal point:\n        //         - `\\d`: matches the digit(s) before the decimal point.\n        //         - `\\.`: matches the decimal point.\n        //         - `[\\d_]*\\d`: matches digits after the decimal point, ensuring the sequence ends in a digit and not a trailing underscore.\n        // 4. `(\\.[\\d_]+)`: This is the third alternative in the main group:\n        //     - `\\.`: matches a literal dot, so this alternative captures numbers that begin with a decimal point.\n        //     - `[\\d_]+`: matches one or more digits or underscores, for the sequence after the initial dot.\n        match:\n          /\\b((\\d[_\\d]*(e|E)\\d[_\\d]*)|(\\d[_\\d]*|(\\d\\.[\\d_]*\\d))|(\\.[\\d_]+))/,\n        relevance: 10,\n      },\n      {\n        // range\n        scope: \"symbol\",\n        match: /\\.{2}/,\n        relevance: 10,\n      },\n      // Unfortunately this just overrides any keywords. It's also not\n      // complete — it only handles functions at the beginning of a line.\n      // I spent several hours trying to get hljs to handle this, but\n      // because there's no recursion, I'm not sure it's possible.\n      // Possibly we could hook into `on:begin` and implement it\n      // ourselves, but this would be a lot of overhead.\n      // { // function\n      //     keywords: TRANSFORMS.join(' '),\n      //     beginScope: { 1: 'title.function' },\n      //     begin: [/^\\s*[a-zA-Z]+/, /(\\s+[a-zA-Z]+)+/],\n      //     relevance: 10\n      // },\n    ],\n  };\n};\n\nhljs.registerLanguage(\"prql\", formatting);\n\n// This line should only exists in the website, not the book.\n\nhljs.highlightAll();\n"
  },
  {
    "path": "web/website/themes/prql-theme/static/style.css",
    "content": "/* Inter ExtraBold */\n@font-face {\n  font-family: \"InterExtraBold\";\n  font-weight: 800;\n  font-style: normal;\n  font-display: swap;\n  src:\n    local(\"\"),\n    url(\"../fonts/inter-extra-bold.woff2\") format(\"woff2\"),\n    url(\"../fonts/inter-extra-bold.woff\") format(\"woff\");\n}\n\n/*--------------------------------------------------------------\n# General\n--------------------------------------------------------------*/\n:root {\n  --main-color: #202b38;\n  --secondary-color: #c92a2a;\n  --third-color: #1864ab;\n  --title-font:\n    InterExtraBold, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\n    Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n  --body-font:\n    -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu,\n    Cantarell, \"Helvetica Neue\", sans-serif;\n}\n\nbody {\n  font-family: var(--body-font);\n  color: var(--main-color);\n}\n\na:hover {\n  color: var(--secondary-color);\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-family: var(--title-font);\n  font-weight: 900;\n}\n\na.btn {\n  background-color: var(--main-color);\n  color: #fff;\n  font-size: 20px;\n  padding: 10px 20px;\n}\n\na.btn:hover {\n  color: #fff;\n}\n\na.btn-icon {\n  display: inline-flex !important;\n  font-size: 25px;\n  padding: 5px !important;\n  margin: 0.5rem 1rem;\n  color: #fff !important;\n  background: var(--third-color);\n  border-radius: 50%;\n  transition: all 0.3s;\n  text-decoration: none;\n}\n\na.btn-icon:hover {\n  background: var(--main-color);\n}\n\na.btn-icon i {\n  width: 1.5em;\n  line-height: 1.5;\n  text-align: center;\n}\n\n/*--------------------------------------------------------------\n# Back to top button\n--------------------------------------------------------------*/\n.back-to-top {\n  position: fixed;\n  visibility: hidden;\n  opacity: 0;\n  right: 15px;\n  bottom: 15px;\n  z-index: 996;\n  background: var(--main-color);\n  width: 40px;\n  height: 40px;\n  border-radius: 4px;\n  transition: all 0.4s;\n  text-decoration: none;\n}\n\n.back-to-top i {\n  font-size: 28px;\n  color: #fff;\n  line-height: 0;\n}\n\n.back-to-top:hover {\n  background: var(--secondary-color);\n  color: #fff;\n}\n\n.back-to-top.active {\n  visibility: visible;\n  opacity: 1;\n}\n\n/* Code */\n\ncode.hljs {\n  padding: 0.75rem 1rem;\n  border-radius: 8px;\n  background-color: var(--main-color);\n  color: #ffffff;\n}\n\ncode:not(.hljs) {\n  padding: 1px 0.4rem;\n  border-radius: 4px;\n  background-color: #ccc;\n  color: var(--main-color);\n}\n\n/* width */\npre ::-webkit-scrollbar {\n  height: 6px;\n}\n\n/* Track */\npre ::-webkit-scrollbar-track {\n  background: var(--main-color);\n}\n\n/* Handle */\npre ::-webkit-scrollbar-thumb {\n  background: #000;\n  border-radius: 4px;\n  width: 2px;\n}\n\n/* Handle on hover */\npre ::-webkit-scrollbar-thumb:hover {\n  background: rgb(109, 109, 109);\n}\n\n/*--------------------------------------------------------------\n# Header\n--------------------------------------------------------------*/\n#header {\n  background: white;\n  z-index: 997;\n  padding: 15px 0;\n  border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n#header .logo {\n  max-height: 50px;\n  text-decoration: none;\n}\n\n#header .logo h1 {\n  margin: 0;\n}\n\n#header a:not(.btn-icon) {\n  color: var(--main-color);\n  transition: 0.2s;\n}\n\n#header a:hover,\n#header .active,\n#header .active:focus,\n#header li:hover > a {\n  color: var(--secondary-color);\n}\n\n/*--------------------------------------------------------------\n# Hero Section\n--------------------------------------------------------------*/\n.hero {\n  width: 100%;\n  min-height: calc(100vh - 81px);\n  background: #f2f9ff;\n  background:\n    repeating-linear-gradient(\n      45deg,\n      transparent,\n      transparent 150px,\n      #f2f9ff 150px,\n      #f2f9ff 300px\n    ),\n    linear-gradient(#daeaff, #f2f9ff);\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  flex: 1 0 fit-content;\n}\n\n.hero h4 {\n  font-size: 20px;\n  color: var(--main-color);\n}\n\n.hero h4 span {\n  color: #c92a2a;\n}\n\n.hero h2 {\n  font-size: 45px;\n  color: var(--main-color);\n  font-weight: 700;\n}\n\n.hero .bottom-text {\n  font-size: 20px;\n  color: #4b5157;\n}\n\n.hero a.btn {\n  color: #fff;\n  background-color: var(--third-color);\n  padding: 10px 20px;\n  font-size: 23px;\n  font-weight: 500;\n  margin-top: 20px;\n  transition: 0.3s;\n}\n\n.hero a.btn:hover {\n  background-color: var(--main-color);\n}\n\n@media (max-width: 992px) {\n  #hero {\n    height: 100vh;\n  }\n\n  #hero .carousel-container {\n    top: 8px;\n  }\n}\n\n@media (max-width: 768px) {\n  #hero h2 {\n    font-size: 28px;\n  }\n}\n\n@media (min-width: 1024px) {\n  #hero .carousel-control-prev,\n  #hero .carousel-control-next {\n    width: 5%;\n  }\n}\n\n@media (max-height: 500px) {\n  #hero {\n    height: 120vh;\n  }\n}\n\n.callout {\n  padding: 1.25rem 1.25rem 0.5rem 1.25rem;\n  margin-top: 1.25rem;\n  margin-bottom: 1.25rem;\n  background-color: #fff;\n  border-left: 0.25rem solid #c92a2a;\n}\n\n/*--------------------------------------------------------------\n# Sections General\n--------------------------------------------------------------*/\nsection {\n  padding: 2rem 0;\n  overflow: hidden;\n}\n\n.section-bg,\n.services .icon-box {\n  background: #f2f9ff;\n  background:\n    repeating-linear-gradient(\n      45deg,\n      transparent,\n      transparent 150px,\n      #f2f9ff 150px,\n      #f2f9ff 300px\n    ),\n    linear-gradient(#daeaff, #f2f9ff);\n}\n\n.section-title h2 {\n  font-size: 45px;\n  font-weight: 700;\n  padding: 0;\n  color: var(--main-color);\n}\n\n.section-title h3 {\n  font-size: 22px;\n  font-weight: 600;\n  padding: 0;\n  color: var(--third-color);\n}\n\n.section-title p {\n  margin: 0;\n  margin: 0;\n  font-size: 18px;\n  font-weight: 400;\n  color: var(--main-color);\n}\n\n/*--------------------------------------------------------------\n# Cards\n--------------------------------------------------------------*/\n.big-cards-section {\n  background: #f2f9ff;\n}\n\n.big-cards-section .card {\n  padding: 30px;\n  height: 100%;\n}\n\n.big-cards-section .card i {\n  color: #202b38;\n  font-size: 52px;\n}\n\n.big-cards-section .card h4 {\n  font-weight: 600;\n  font-size: 24px;\n  line-height: 60px;\n  margin-bottom: 20px;\n  color: var(--secondary-color);\n}\n\n.big-cards-section .card h4::after {\n  content: \"\";\n  width: 50%;\n  height: 2px;\n  display: block;\n  background: var(--third-color);\n  margin: 4px 0px;\n}\n\n.big-cards-section .card p {\n  color: var(--main-color);\n}\n\n/*--------------------------------------------------------------\n# showcase-section\n--------------------------------------------------------------*/\n.showcase-section {\n  background: #f2f9ff;\n}\n\n.showcase-section #v-pills-tabContent {\n  flex-grow: 1;\n}\n\n.nav-pills .nav-link {\n  color: var(--third-color);\n}\n\n.nav-pills .nav-link.active {\n  color: #fff;\n  background-color: var(--main-color);\n}\n\n/*--------------------------------------------------------------\n# content-section\n--------------------------------------------------------------*/\n.cards-section {\n  background: #f2f9ff;\n}\n\n.content-section .icon {\n  font-size: 250px;\n  color: var(--main-color);\n}\n\n.content-section .card {\n  padding: 1.5em;\n  height: 100%;\n  flex: 100%;\n}\n\n.content-section .card a {\n  color: var(--third-color);\n}\n\n.content-section .card a:hover {\n  color: var(--secondary-color);\n}\n\n.content-section h3 {\n  color: var(--third-color);\n  font-weight: 600;\n}\n\n.content-section ul {\n  list-style: none;\n}\n\n.content-section ul li P {\n  font-size: 22px;\n  color: var(--main-color);\n}\n\n.content-section ul li P i {\n  font-size: 12px;\n  padding: 3px;\n}\n\n/*--------------------------------------------------------------\n# testimonials-section\n--------------------------------------------------------------*/\n.testimonials-section {\n  background:\n    repeating-linear-gradient(\n      45deg,\n      transparent,\n      transparent 150px,\n      #f2f9ff 150px,\n      #f2f9ff 300px\n    ),\n    linear-gradient(#f2f9ff, #daeaff);\n}\n\n.quote .quote-text {\n  height: 100%;\n  margin: 0;\n\n  display: flex;\n  flex-direction: column;\n  gap: 2rem;\n  justify-content: space-between;\n}\n\n.quote span {\n  display: block;\n  font-weight: bold;\n}\n\n.quote a:link,\n.quote a:visited {\n  text-decoration: none;\n  color: inherit;\n}\n.tweet-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.tweet-author {\n  display: flex;\n  position: relative;\n  align-items: center;\n}\n.tweet-author-image {\n  width: 48px;\n  height: 48px;\n  border-radius: 9999px;\n}\n.tweet-author-info {\n  margin-left: 1rem;\n}\n.tweet-author-name {\n  line-height: 1rem;\n  font-weight: 500;\n  margin: 0;\n}\n.tweet-author-handler {\n  line-height: 1.8rem;\n}\n\n.tweet-logo {\n  width: 20px;\n  height: 20px;\n  position: absolute;\n  top: -4px;\n  left: -8px;\n  background: var(--third-color);\n  color: #fff;\n  border-radius: 9999px;\n  padding: 0.2rem;\n}\n\n/*--------------------------------------------------------------\n# Article\n--------------------------------------------------------------*/\n\narticle {\n  padding: 4rem 0;\n  font-size: 1.1em;\n}\n\narticle h1 {\n  margin-bottom: 2rem;\n}\n\narticle h2 {\n  margin: 2rem 0 1rem 0;\n}\n\narticle img {\n  max-width: 100%;\n}\n\nblockquote p {\n  margin: 0 0 1rem 0.5rem;\n  padding: 0.25rem 1.25rem;\n  border-left: 0.25rem solid lightgrey;\n}\n\narticle > blockquote > *:last-child {\n  margin-bottom: 0;\n}\n\narticle > pre,\narticle > .highlight {\n  margin: 0 -0.5rem 1rem -0.5rem;\n}\n\narticle > .highlight > pre {\n  background-color: transparent !important;\n}\n\n/*--------------------------------------------------------------\n# Footer\n--------------------------------------------------------------*/\n#footer {\n  /*\n  TODO: the background was a dark color, but it made the links quite difficult to read.\n  Probably I (@max-sixty) shouldn't be choosing colors — so please feel free to\n  adjust.\n  */\n  background: #cbdcef;\n  /* background: linear-gradient(#58718d, var(--main-color)); */\n  padding: 1rem 0 2rem 0;\n  /* color: #fff; */\n}\n\n/* #footer p {\n  color: #bbb;\n} */\n\n/* #footer a.btn {\n  background-color: var(--secondary-color);\n} */\n\n#footer ul {\n  padding-left: 1rem;\n  /* color: #bbb; */\n}\n\n/* #footer .copyright {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding-top: 30px;\n}\n\n#footer .copyright i {\n  font-size: 28px;\n  color: rgba(219, 219, 219, 0.712);\n  padding-left: 20px;\n}\n\n#footer .copyright i:hover {\n  color: #fff;\n}\n\n#footer .copyright p {\n  display: flex;\n} */\n\n/*--------------------------------------------------------------\n# FAQ sections\n--------------------------------------------------------------*/\ndetails.faq h2 {\n  display: inline-flex;\n  align-items: center;\n  margin-bottom: 0;\n  font-size: 26px;\n  white-space: pre-wrap;\n}\ndetails.faq[open] summary h2 {\n  margin-bottom: 1rem;\n}\ndetails.faq summary::-webkit-details-marker {\n  display: none;\n}\ndetails.faq summary {\n  list-style: none;\n}\ndetails.faq summary h2::before {\n  content: \"►\";\n  font-size: 16px;\n  margin-right: 5px;\n}\ndetails.faq[open] summary h2:before {\n  content: \"▼\";\n}\n\n/*--------------------------------------------------------------\n# Table of contents\n--------------------------------------------------------------*/\n\n.toc > * {\n  position: fixed;\n  padding: 4rem 0;\n}\n.toc ul li {\n  list-style: none;\n  margin: 0;\n  padding-top: 0.5em;\n}\n\n.toc ul li ul {\n  display: none;\n}\n\n/*--------------------------------------------------------------\n# List\n--------------------------------------------------------------*/\n\n.list h1 {\n  margin-bottom: 2rem;\n}\n\n.list .post-item {\n  padding: 0.75rem 0;\n}\n\n.list .post-item > a {\n  font-family: var(--title-font);\n}\n\n/*--------------------------------------------------------------\n# \"Full screen\" iframe views for book, playground etc\n--------------------------------------------------------------*/\n.big-iframe {\n  flex-grow: 1;\n}\n.big-iframe iframe {\n  height: 100%;\n  width: 100vw;\n  display: block;\n  border: none;\n}\n"
  },
  {
    "path": "web/website/themes/prql-theme/theme.toml",
    "content": "# theme.toml template for a Hugo theme\n# See https://github.com/gohugoio/hugoThemes#themetoml for an example\n\nlicense = \"Apache 2.0\"\nmin_version = \"0.41.0\"\nname = \"PRQL Theme\"\n"
  }
]